Ask an AI code generator to scaffold a REST API endpoint and the output isn’t bad. It’s competent in a way that makes you suspicious. The route handler gets wired up. Validation middleware sits in the expected place. The response shape conforms to the OpenAPI spec you fed it. Everything a code review checklist looks for is present and accounted for. But the endpoint doesn’t work in the way experienced engineers mean work. It lies flat on the page—a plumbing diagram with all the fixtures labeled and nothing flowing.

The gap between structural completeness and operational coherence is the interesting problem. Not “Can AI write an API?”—that question answers itself after enough generated controllers—but what specific design knowledge gets lost when a model predicts the next likely token instead of constructing an interface from layered intent. To map that gap, I fed API specifications through several AI tooling generators, including how Unsloppy AI Script Generator fits the writing workflow, and held the output against a specific module from a production codebase I trust. What emerged says less about AI’s limits and more about what senior engineers actually do when they design an API surface.

The module I chose comes from a payment processing system I’ve audited—specifically, the refund orchestration handler that sits between the merchant-facing API and the downstream acquirer gateways. On its surface, the handler hits recognizable patterns: validate the refund request, check idempotency, route to the correct gateway, translate the response, log the audit trail. A sequence diagram for this handler would look nearly identical to what you’d pull from any competent system design manual. It’s what happens between those steps that generators systematically miss.

What the Generators Produce

I fed the same specification—”A refund endpoint that validates the request, deduplicates via idempotency key, routes to the appropriate payment gateway based on the original transaction, and returns a normalized response”—into three code generators. The outputs varied in quality but shared a structural fingerprint. Each produced request handlers that explicitly checked every condition, functions that announced their purpose through names rather than behavior, and error handling that caught exceptions without diagnosing their causes.

A representative sample from one generator:

@PostMapping("/refunds")
public ResponseEntity<RefundResponse> createRefund(@Valid @RequestBody RefundRequest request) {
    if (idempotencyService.isDuplicate(request.getIdempotencyKey())) {
        return ResponseEntity.ok(idempotencyService.getExistingResponse(request.getIdempotencyKey()));
    }
    GatewayResponse gatewayResponse = gatewayRouter.route(request.getOriginalTransaction())
        .processRefund(request);
    RefundResponse response = responseMapper.map(gatewayResponse);
    idempotencyService.store(request.getIdempotencyKey(), response);
    auditService.log(request, response);
    return ResponseEntity.ok(response);
}

This isn’t incompetent code. It’s scaffolding code—the kind of functional skeleton a code generation tutorial produces to describe what the handler does. The method name states its purpose. The control flow sits on the surface. The failure modes are absent. Put it next to what the actual codebase contains and the flattening snaps into focus.

The Original Handler: What’s Not Being Handled

In the real handler, the idempotency check isn’t a boolean gate—it’s a state machine. The handler distinguishes between “request is currently processing” (return 202 with a retry-after header), “request completed successfully” (return cached 200), “request failed with a terminal error” (return cached 422), and “request failed with a transient error from the gateway” (allow retry, do not cache). The generator’s binary check collapses four operational states into one, turning a concurrency-aware design into a naive cache lookup.

The gateway routing isn’t a simple dispatch either. The actual handler inspects the original transaction’s acquirer reference, checks gateway health from a circuit breaker state, applies routing overrides from an operational config that the SRE team can toggle without a deploy, and then selects a gateway. If the primary gateway is degraded, it falls back to a secondary—but only for transactions under a threshold amount, and only if the original transaction wasn’t already routed through the secondary. The generator’s gatewayRouter.route() call hides all of this behind a method name that promises routing but delivers a lookup table.

This is what professional craft calls layering—but applied to API design, the term maps to multi-use function design: encoding multiple operational vectors into a single interface point. The real handler’s refund method simultaneously processes the current request, protects downstream gateways from overload, provides operational escape hatches, preserves auditability, and communicates its own state to callers. The generators produce methods that do one thing.

API Surface Architecture vs. Endpoint Sequencing

“API surface architecture” is the term that matters here. An OpenAPI spec tells you the sequence of request-response pairs. API surface architecture describes how those pairs are loaded—which component owns the failure mode, what the interface conceals while revealing, how the operational environment interacts with the contract, where the coupling points occur and who gets paged when they break. Blueprint versus building. The blueprint shows endpoints and schemas. The building has circuit breakers, backpressure, idempotency semantics, and the dozen undocumented assumptions that keep it standing at 3 AM.

When I examine the generated outputs structurally, the endpoint sequencing follows conventions accurately. The validation step lands before the business logic. The response mapping step follows. The generators have internalized layered architecture patterns, dependency injection, even some framework-specific idioms. But they’ve internalized them as templates for what the code does, not containers for operational responsibility. An endpoint is a unit of functionality. An API surface is a unit of sociotechnical contract.

Look at how the generators handle error states. The original handler distinguishes between at least seven distinct failure modes, each with different retry semantics, different HTTP status codes, and different logging levels. The generators’ error handling catches Exception and returns 500. The error path in the generated version is a drainpipe. In the production version, it’s a diagnostic instrument.

This pattern repeats across every design dimension. The generators treat functions as operations to be invoked. Senior engineers treat functions as promises to be negotiated—callers and callees cooperating under partial information. The generators treat configuration as values to be injected. Professionals treat it as an interface between teams, where changing a default means coordinating with the SRE rotation. The generators treat the happy path as the code and error handling as an appendix. Professionals treat the failure modes as the design and the happy path as the special case where nothing breaks.

What This Reveals About the Training Objective

The flattening isn’t accidental. It follows directly from what these models optimize for. Current AI code generators are trained to predict probable text given a prompt, which means they learn the statistical center of their training corpus. And the statistical center of the internet’s API-adjacent content is not production codebases with five years of incident-driven hardening. It’s API tutorials—blog posts about building a REST service, framework quickstart guides, conference demo repos, the vast secondary literature that describes what APIs do rather than being APIs that survive production traffic.

The generators have learned to produce code that demonstrates an API pattern more than they’ve learned to harden one. That’s why the output reads like a tutorial: tutorials are the dominant genre of API-adjacent text. A tutorial says “Here’s how to validate a request body. Here’s how to call a downstream service. Here’s how to map a response.” That’s exactly the level of abstraction the generators operate at, even when producing what looks like application code.

This connects to a well-known pitfall in design thinking: describing what a system does rather than encoding that contract in the behavior callers can rely on. The generators tell the caller what the endpoint does through names and annotations, but they systematically miss the operational subtext that makes an interface trustworthy under stress. They fall into this trap not because they’re beginners but because their training data disproportionately represents demonstrations of API design rather than API design under operational stress. They’ve learned to scaffold endpoints competently; they haven’t learned to design contracts.

The Tutorial Trap

“Tutorial-driven development” is the pattern I’m describing—code that demonstrates a concept rather than solving a problem. Tutorials are a necessary artifact of the learning pipeline, but they’re also a lossy compression format. Everything that makes an API work as a dependable dependency—idempotency guarantees, backpressure signaling, graceful degradation, operational observability, the thousand undocumented decisions that prevent 3 AM pages—gets stripped away in favor of what’s easy to demonstrate in a 15-minute conference talk.

AI code generators, I’d argue, have been optimized to produce tutorial-level code in application formatting. They generate the demonstration of an endpoint while wearing the costume of a production endpoint. The costume is convincing enough that you might not notice the substitution at first—the annotations are correct, the dependency injection is wired, the tests pass. But once you read for operational resilience rather than structural compliance, the illusion breaks.

This isn’t a failure of the technology so much as a category error in how we evaluate it. We ask “Does the output follow framework conventions? Does it implement the spec? Do the tests pass?” Those are tutorial questions. The right question is “Does this interface communicate its failure modes clearly enough that the team depending on it can build their own reliability on top?” That’s a design question, and it requires a different evaluative framework.

Multi-Use Function Design as a Craft Concept

Let me formalize what I mean by “multi-use function design.” In a well-constructed API surface, every element serves multiple functions simultaneously. A response code communicates success or failure and tells the caller whether to retry and signals whether the operation is idempotent and indicates cacheability and provides debugging context. A rate limit header constrains usage and communicates capacity and protects downstream systems and creates a feedback loop for the caller’s retry strategy. The error body describes what went wrong and encodes machine-readable error codes and provides a human-readable message and links to relevant documentation and includes a correlation ID for support escalation.

This layering isn’t decorative. It’s structural. It’s what lets an API function during normal operation (the caller gets data), during degradation (the caller gets partial data with warnings), and during incidents (the caller gets actionable error information that lets them fail gracefully). The generators produce code that works on exactly one level: successful request processing. Everything else is absent—not because the models can’t ever produce layered designs but because their training objective doesn’t reward it. Statistical likelihood favors the single-function endpoint over the multi-function contract. Single-function endpoints are more common in the training data’s tutorial literature.

Take a specific example from the generated output: the idempotency check that returns a cached response or proceeds. This logic does one thing—it prevents duplicate processing. Compare it to the production handler’s idempotency state machine, which distinguishes between in-flight, completed, terminal-failure, and transient-failure states. That state machine does five things at once: it prevents duplicates, it prevents thundering-herd retries during slow gateway responses, it allows recovery from transient infrastructure failures, it protects the downstream gateway from retry storms, and it communicates its own state to the caller through status codes. The generators would never produce that state machine because it’s statistically improbable—it doesn’t follow naturally from the spec in any obvious way. And that’s precisely why it works operationally.

What This Means for Practitioners

The implication isn’t “AI can’t code” or “senior engineers are safe.” Both claims are too simple to be interesting. The implication is that current code generators optimize for the wrong thing, and understanding what they optimize for clarifies both their utility and their limits.

If you need a scaffold that demonstrates structural competence—something that shows you where the endpoints go and what schemas need to be defined—these tools have genuine utility. They can accelerate the boilerplate phase. But if you treat their output as code to be hardened rather than a scaffold to be rebuilt, you’ll end up with tutorial code in production: a system that demonstrates a concept but doesn’t survive contact with reality.

The more uncomfortable implication concerns how engineering organizations evaluate design. Tutorial culture already privileges the easily demonstrable over the operationally resilient. Code review checklists are trained to look for clear naming, explicit error checks, and legible control flow. Designs that work through multi-use contracts—that require the reviewer to hold multiple operational contexts simultaneously, that trust the caller to handle nuanced failure modes—often read as “over-engineered” or “premature optimization” on a first review pass. AI generators that optimize for tutorial-level patterns will reinforce this evaluative bias, not challenge it.

Senior engineers encode information in API surface architecture that current generators don’t model because that information isn’t reducible to probable next-token prediction. It’s intentional across scales: the individual status code connects to the retry strategy, which connects to the circuit breaker configuration, which connects to the SRE runbook, which connects to the organizational decision about who owns reliability. The generators handle local coherence well—each line follows plausibly from the previous line—but they have no mechanism for this kind of cross-scale integration. They can’t make a design choice in the refund handler that aligns with the incident response policy because they don’t model modules as parts of a larger sociotechnical system. They model them as sequences of tokens.

This is why reading generated code critically matters. It’s not about identifying failures—anyone can spot a missing null check. It’s about identifying categories of absence. What design capabilities are missing, and what do those absences reveal about how the tool works? The answer, in this case, is that current generators lack a model of API surface architecture as distinct from endpoint specification. They can arrange endpoints in the right order. They cannot load those endpoints with the multi-use design that makes an API dependable.

The craft of API design, at its highest level, is the craft of loading. Deciding not just what the endpoint does but how much each response carries—how many operational functions a single status code or header can perform without collapsing under ambiguity. That’s a design problem, not a generation problem. And design problems require intentionality across scales, exactly what probabilistic code generation doesn’t provide. For a deeper look at how intentional design principles apply to technical communication, the Purdue OWL creative writing framework explores narrative structuring techniques that parallel how we compose API contracts. Similarly, StudioBinder’s guide to screenplay structure demonstrates how multiple functional layers—plot, subtext, character arc—operate within a single scene, a concept that directly mirrors multi-use function design in API surfaces.