Slopapalooza

What it is

The Duct-Tape API is an API built endpoint by endpoint without shared rules for resources, authorization, errors, and pagination. Each route can pass its own tests while forcing consumers to learn a different contract. Consumers end up carrying the conventions the server never settled.

How it happens

Each endpoint was prompted independently. “Add a route for X.” “Now one for Y.” The AI follows whichever local example it sees, and nobody checks whether that example agrees with the rest of the API. The next route inherits another plausible convention.

In an illustrative orders API, the list endpoint filters results to the signed-in customer, but GET /orders/{id} checks only that someone is signed in. A client can follow the happy path successfully while the detail endpoint exposes another customer’s order. Matching URL names would not repair that gap. The shared contract needs an ownership rule and a test using a different customer’s ID.

Warning signs

  • Routes exposing the same private resource apply different ownership checks.
  • The same failure returns JSON on one route, a string on another, and an undocumented status on a third.
  • Verbs and nouns drift: GET /getUser beside POST /users/create.
  • Pagination changes from cursors to offsets without an explicit reason or documented contract.
  • Each new endpoint reimplements parsing, validation, and error handling.

Why it’s dangerous

The cost lands on consumers later and louder. They need special cases for every route, and a generic retry or error handler may do the wrong thing. Inconsistent authorization can be more than integration friction: it can expose data or permit an action the caller should not have.

AI can generate handlers quickly enough that the inconsistent surface grows before anyone tries to consume it as a whole. Once clients rely on those differences, cleanup becomes a compatibility project.

How to prevent it

Before adding a route, state its resource model, authorization rules, request and response shapes, errors, and pagination behavior. Reuse the API’s conventions where they fit and document deliberate exceptions. Give AI the contract and representative existing routes as context.

For an existing API, inventory actual consumers before changing public behavior. Internal helpers can often be consolidated while preserving responses. A breaking change may need a new version and a migration period; not every inconsistency requires a wholesale v2. Fix an authorization exposure promptly — unauthorized access is not compatibility worth preserving. For legitimate consumers, plan deprecation around migration rather than a date somebody hoped for.

The serious team fix

  1. Write the shared rules in the repo. Include examples of successful responses, failures, and authorization decisions. Specify what the caller may access, not just which authentication header to send. Use shared authentication and authorization mechanisms where the policy is shared, with resource-specific checks at the appropriate boundary. Otherwise every generated route has to get the same security decision right independently.
  2. Describe the interface in a machine-readable contract. The OpenAPI Specification describes HTTP interfaces, including operations, parameters, responses, and security requirements. Tooling can use that description for documentation and code generation. It does not by itself enforce a rule such as “this customer owns this order”; the implementation and behavior checks must still do that work.
  3. Check both the description and the running API. Lint the contract for agreed conventions. Exercise the service with allowed and denied callers, invalid inputs, and representative pagination cases. For the orders example, a two-customer test should reject the other customer’s ID on the detail route as well as omit it from the list. Run those checks against the application with its real authorization configuration; stubbing out the permission check would miss the defect.

Related anti-patterns

Back to all anti-patterns