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-openapigenerates 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: trueandSunsetheaders 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_ateverywhere — nevertimestamp,modified, orchanged
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, maximumlimit: 100 - Use cursor-based pagination (see patterns.md)
- Return
total_countonly 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=teamto 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 likeAuthorizationare 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-Keyheader 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 Conflictif 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
Acceptheader - Return 406 Not Acceptable if the requested format is not supported
- Default to
application/jsonwhen 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 |