Skip to content

API Pitfalls

Known failure modes in API design and implementation. Every pitfall here has either happened in GE or has been observed in client projects. Learn from them.


1. Spec Drift

What happens: The OpenAPI spec says one thing, the implementation does another. Fields are added to responses without updating the spec. New endpoints appear without spec entries. Validation rules in code diverge from schema definitions.

Why it is dangerous: The spec becomes unreliable. Agents generating client code from the spec produce broken clients. Contract tests pass against an outdated spec. Consumers lose trust in the documentation.

How GE prevents it:

  • Koen runs spec-implementation drift detection in CI
  • Jaap enforces the spec as SSOT — no exceptions
  • @hono/zod-openapi generates the spec from the same schemas used for runtime validation — single source, zero drift

Rule: If you change the implementation, update the spec first. If you cannot update the spec first, you are not doing API-first.


2. Breaking Changes Without Versioning

What happens: A developer renames a response field, changes a type from string to integer, removes a deprecated field, or adds a required request field — without bumping the API version.

Why it is dangerous: Every consumer breaks silently. In an agentic system, this means agents fail on tasks they were completing yesterday. Debugging is painful because the error looks like a consumer bug.

Prevention:

  • Run breaking-change detection in CI (compare current spec vs previous)
  • Any removal, rename, or type change triggers a version bump
  • Use Deprecation: true and Sunset headers for 30 days before removal
  • Anna's spec includes a compatibility checklist for every endpoint change

3. Inconsistent Naming

What happens: One endpoint uses created_at, another uses createdAt, a third uses creation_date. One resource calls it userId, the related resource calls it user_id.

Why it is dangerous: Agents (and humans) cannot predict field names. Code generation produces inconsistent types. Typo-based bugs multiply.

GE convention:

  • snake_case for all JSON field names
  • kebab-case for URL path segments
  • camelCase only in TypeScript interfaces (auto-mapped from snake_case)
  • Consistent vocabulary: created_at, updated_at, deleted_at everywhere — never timestamp, modified, or changed

4. Over-Fetching and Under-Fetching

What happens:

  • Over-fetching: A list endpoint returns the full object with all nested relations. A mobile client downloading a list of 50 projects gets 2 MB of data it does not need.
  • Under-fetching: A detail endpoint omits related data, forcing the client to make 5 additional requests to assemble a complete view.

Prevention:

  • Use sparse fieldsets for over-fetching: ?fields=id,name,status
  • Use includes for under-fetching: ?include=tasks,team
  • Design dedicated list vs detail schemas — the list schema is lean, the detail schema is complete
  • Consider whether GraphQL is more appropriate (it rarely is for agent-to-agent communication, but it can help for complex client UIs)

In agentic context: Agents do not need every field. Over-fetching wastes tokens when agents process API responses through their context window. Design lean responses.


5. Missing Pagination

What happens: A list endpoint returns all records. Works fine with 10 items in development. Falls over with 50,000 items in production. The response takes 8 seconds, the client times out, the user retries, and the server is now handling 3 concurrent full-table scans.

Prevention:

  • Every list endpoint must paginate. No exceptions.
  • Default limit: 20, maximum limit: 100
  • Use cursor-based pagination (see patterns.md)
  • Return total_count only when the table is small enough to compute it cheaply

6. N+1 in REST

What happens: A list endpoint returns 50 projects. The client needs the team name for each project. The client makes 50 additional requests to /teams/{teamId}. The backend handles 51 requests for what should be one query.

Why it is common in agentic systems: Agents follow specs literally. If the list response does not include the team name, the agent will dutifully call the team endpoint for each project. Agents do not optimize request patterns unless the spec tells them to.

Prevention:

  • Support ?include=team to embed related resources
  • Return commonly-needed foreign key data inline: "team": { "id": "team_alfa", "name": "Team Alfa" }
  • Document the include parameter in the spec
  • Antje's tests should verify that list + include returns the nested data without additional requests

7. Auth Token in URL

What happens: Authentication tokens are passed as query parameters: /api/projects?token=abc123. The URL appears in server access logs, browser history, proxy logs, and referrer headers.

This is a security incident, not a pitfall.

Rule: Tokens go in the Authorization header. Always. The only exception is WebSocket upgrade requests where headers are not supported — use a short-lived, single-use token in the URL and exchange it immediately.


8. CORS Misconfiguration

What happens:

  • Access-Control-Allow-Origin: * in production — any origin can call the API with credentials
  • Missing CORS headers entirely — browser-based consumers cannot call the API at all
  • Forgetting Access-Control-Allow-Headers — custom headers like Authorization are blocked
  • Not handling OPTIONS preflight — preflight fails, the actual request never fires

Prevention:

  • Whitelist specific origins: https://app.growingeurope.io
  • Include credentials support: Access-Control-Allow-Credentials: true
  • List all custom headers: Authorization, Content-Type, X-Request-Id
  • Handle OPTIONS with 204 and appropriate max-age
  • Test CORS from a browser — curl does not enforce CORS

9. Inconsistent Error Responses

What happens: The validation endpoint returns { "error": "Invalid email" }. The auth endpoint returns { "message": "Unauthorized", "code": 401 }. The file upload endpoint returns plain text: "File too large".

Why it is dangerous in agentic systems: Agents parse error responses to decide what to do next. If every endpoint returns errors in a different shape, the agent needs endpoint-specific error handling. This does not scale with 60 agents and hundreds of endpoints.

Rule: RFC 9457 Problem Details format for every error. No exceptions. See patterns.md.


10. Missing Idempotency

What happens: The client sends a POST to create an order. The request times out. Did it succeed? The client retries. Now there are two orders.

Prevention:

  • Accept an Idempotency-Key header on non-idempotent endpoints
  • Store the key and response for 24 hours
  • On duplicate key, return the stored response (do not re-execute)
  • Return 409 Conflict if the same key is used with different parameters

In agentic context: Network failures between agents are common. Retry logic is built into the executor. Without idempotency keys, retries create duplicates. Every state-changing endpoint must support idempotency.


11. Not Using 409 for Business Logic Conflicts

What happens: A task is already completed. The agent sends a PATCH to update it. The API returns 400 ("Task cannot be updated") or worse, 500 ("Invalid state transition").

Correct response: 409 Conflict with Problem Details explaining the state conflict:

{
  "type": "https://api.growingeurope.io/errors/invalid-state-transition",
  "title": "Invalid State Transition",
  "status": 409,
  "detail": "Task task_abc is in state 'completed' and cannot transition to 'in_progress'."
}

12. Exposing Internal IDs

What happens: Database auto-increment IDs (1, 2, 3) are used as resource identifiers. Competitors can enumerate resources. Users can guess other users' data.

Prevention:

  • Use UUIDs or prefixed IDs (proj_abc123, task_xyz789)
  • Prefixed IDs are more readable in logs and easier to identify across services
  • Never expose database row IDs in API responses

13. Ignoring Accept Headers

What happens: The API only returns JSON but does not check the Accept header. A client requesting application/xml gets JSON back with a 200 status, breaking the client's XML parser.

Prevention:

  • Check Accept header
  • Return 406 Not Acceptable if the requested format is not supported
  • Default to application/json when no Accept header is present

Summary

Pitfall Severity Detection
Spec drift Critical Koen CI check
Breaking changes Critical Spec diff in CI
Inconsistent naming High Linting rules
Over/under-fetching Medium Performance review
Missing pagination Critical Load testing
N+1 in REST High Antje integration tests
Auth token in URL Critical Security scan
CORS misconfiguration High Browser testing
Inconsistent errors High RFC 9457 enforcement
Missing idempotency High Retry testing
Wrong status codes Medium Contract tests
Exposing internal IDs Medium Security review
Ignoring Accept headers Low Contract tests

Ownership

Role Agent Responsibility
Backend Team Lead (Alfa) Urszula Review for pitfalls in PRs
Backend Team Lead (Bravo) Maxim Review for pitfalls in PRs
Code Quality Koen Automated detection
Adversarial Testing Ashley Actively exploit these pitfalls