Skip to main content
← The Owens Vibe Coding & Development Institute
Rigor toolkit

API Design Checklist

Is this API consistent, validated, versioned, and safe? The endpoint-by-endpoint pass.

API Design Checklist

Use this before you ship — your future self (and your users) will thank you.

A well-designed API is a contract. Once consumers depend on it, every mistake you shipped becomes a legacy you maintain forever. Run through this checklist before any endpoint goes live: during the Investigate, Loop, and Deploy phases of the B.U.I.L.D. method, or any time you're reviewing a pull request that touches your API surface.


Resource Design

  • URLs use nouns, not verbs (/orders, not /getOrders or /fetchOrder)
  • Collection endpoints are plural (/users, /products, /invoices) — consistently, always
  • Singleton sub-resources use singular form where it makes sense (/users/42/profile)
  • Nesting is at most one level deep for sub-resources (/orders/7/items is fine; /users/3/orders/7/items/2/notes is not)
  • Resource IDs are in the path, not the query string, when identifying a specific record
  • URLs are lowercase, hyphen-separated (/order-items, not /orderItems or /order_items)
  • A stranger can guess the URL for a related resource without reading the docs

HTTP Correctness

  • GET — reads only; never modifies state; safe and idempotent
  • POST — creates a resource or triggers an action; not idempotent by default
  • PUT — full replacement of a resource; idempotent (calling it twice has the same effect as once)
  • PATCH — partial update; document whether it is idempotent
  • DELETE — removes the resource; idempotent (deleting twice = same result as deleting once)
  • Status codes are semantically correct:
    • 200 OK — successful read or update with a response body
    • 201 Created — resource was created; Location header points to the new resource
    • 204 No Content — success with no response body (e.g., DELETE, some PATCHes)
    • 400 Bad Request — client sent malformed or unparseable data
    • 401 Unauthorized — caller is not authenticated (no valid token/session)
    • 403 Forbidden — caller is authenticated but not authorized for this resource
    • 404 Not Found — resource does not exist (or you are hiding it from unauthorized callers)
    • 409 Conflict — state conflict (e.g., duplicate unique field, concurrent edit)
    • 422 Unprocessable Entity — data parsed fine but failed business/schema validation
    • 429 Too Many Requests — rate limit exceeded; include Retry-After header
    • 500 Internal Server Error — never returned deliberately; only for unhandled exceptions
  • You never return 200 OK with { "success": false } buried in the body — use the status code

Request Validation

  • Every endpoint validates input server-side — no exceptions, no matter what the frontend promises
  • Required fields are checked for presence (not just truthy — 0 and "" may be valid)
  • Field types are enforced (string vs number vs boolean, not just "it was sent")
  • String lengths have max bounds to prevent oversized payloads
  • Numeric fields have range bounds where the domain demands it (age ≥ 0, quantity ≥ 1)
  • Enum fields are validated against the allowed set — unknown values are rejected, not silently ignored
  • Nested objects and arrays are validated recursively (validating the outer shape is not enough)
  • Validation errors return structured details: which field failed, why, what was received
  • You use a schema library (Zod, Joi, Pydantic, class-validator, JSON Schema, etc.) — hand-rolled if-checks drift
  • You reject unknown/extra fields or at minimum do not pass them downstream

Responses

  • All endpoints return a consistent envelope shape (e.g., { data, error, meta } — pick one and stick to it)
  • List endpoints are paginated — no endpoint returns unbounded arrays; document the default and max page sizes
  • Pagination uses a consistent strategy (cursor-based preferred for large/changing sets; offset acceptable for small sets)
  • Pagination metadata is included in the response (total, nextCursor, hasMore, etc.)
  • Filtering and sorting are allowlisted — callers can only filter/sort on fields you explicitly permit
  • Response objects never include internal database fields (internal_id, stripe_secret_key, password_hash, deleted_at if soft-delete)
  • Response objects never include secrets or credentials even in error messages
  • Timestamps are in ISO 8601 UTC (2026-06-03T14:30:00Z), not Unix epoch integers mixed with locale strings
  • Empty collections return [], not null or a missing key
  • Error responses include a machine-readable code (e.g., "error": "DUPLICATE_EMAIL") alongside the human message

Authentication & Authorization

  • Every protected endpoint explicitly checks authentication before touching any data
  • Every protected endpoint explicitly checks ownership/authorization — authenticated ≠ authorized
  • Returning 404 instead of 403 for resources the caller doesn't own is a deliberate choice (security through obscurity); document it
  • Auth tokens are validated on every request — no "we checked it when they logged in" assumptions
  • Token expiry is enforced; expired tokens return 401
  • JWTs (if used) are verified with the secret/public keyalg: none attacks are not possible
  • Admin-only and elevated-privilege routes are gated by role/scope checks, not just authentication
  • Password/secret fields are never returned in any response, even for the account owner
  • CORS policy is explicit and restrictive — not Access-Control-Allow-Origin: * on authenticated routes

Resilience & Safety

  • Rate limiting is applied at the API layer (per IP, per API key, or per user — document the limits)
  • 429 responses include a Retry-After header so clients can back off correctly
  • Non-idempotent operations (POST, some PATCHes) support an idempotency key header so clients can safely retry
  • All outbound HTTP calls (to third-party APIs, databases, queues) have explicit timeouts
  • Timeouts and downstream failures return a consistent error, not a hanging connection
  • Large file uploads are size-capped at the HTTP layer, not just in application logic
  • SQL queries use parameterized statements — never string interpolation with user input
  • Sensitive operations are logged (auth failures, permission denials, admin actions) for audit purposes
  • Logs never contain plaintext secrets, passwords, or full credit card numbers

Versioning & Evolution

  • The API has a versioning strategy before the first consumer ships (/v1/, header versioning, etc.)
  • New fields added to responses are additive — existing clients are not broken
  • Fields are never removed or renamed without a new version
  • Request shapes are never made more restrictive (new required fields) without a new version
  • Deprecation is communicated via a Deprecation or Sunset response header before removal
  • Breaking changes ship under a new version path; old version stays alive through the deprecation window
  • You have a documented sunset policy (e.g., "old versions supported for 12 months after new version ships")

Documentation

  • Every endpoint is documented: method, path, description, request shape, response shape, error codes
  • Each field in the docs includes its type, whether it's required, and valid values/range
  • Docs include at least one working example per endpoint (request + response, realistic values)
  • Auth requirements are documented per endpoint, not just in a preamble
  • Rate limits are documented
  • An OpenAPI (Swagger) spec exists, is version-controlled, and stays in sync with the implementation
  • The OpenAPI spec is used to generate client SDKs or documentation — not maintained separately by hand
  • A changelog exists so consumers know what changed between versions

Pre-Ship Final Check

  • Run the API through a tool like Postman, Insomnia, or HTTPie — manually hit every new endpoint
  • Test the unhappy paths: missing auth, wrong owner, invalid body, duplicate create, deleted resource
  • Confirm status codes are correct in each unhappy path (not all 400 or all 500)
  • Confirm error responses are structured and informative, not stack traces or raw DB errors
  • Confirm nothing sensitive leaks in error messages or response bodies
  • Peer review: have someone who did not write this code read the endpoint and the docs

Build It Right, Or Don't Build It At All. 🏛️

Other rigor resources

🏛️ Build It Right, Or Don't Build It At All.

API Design Checklist — TOVCDI | HYVE CARES