Guidelines to write sturdy & useful API constructs

This is a list of guidelines I’ve been following to write APIs that behave in predictable patterns and are easy to use for fellow developers.
Since vibe coding is a real thing now, there’s a short prompt at the end to steer LLM coding tools to follow these principles.


What’s an API contract?

Contract = shape + semantics + guarantees

  • Shape: schema (OpenAPI/JSON Schema, Protobuf for gRPC).
  • Semantics: field meaning, errors, pagination, filtering, rate-limits, idempotency.
  • Guarantees: versioning policy, auth model, latency/SLOs, backward compatibility.

Golden rules

  • Versioning: path-based (/v1/...). Deprecate with dates; only additive changes in place.
  • Errors: consistent HTTP 4xx/5xx + machine-parsable bodies (RFC 9457 problem+json).
  • Observability: X-Request-Id, trace context, structured logs.
  • LROs: return 202 Accepted + operation id + polling endpoint or signed webhook (with retries, idempotent).

Pagination (types, trade-offs, choose-by-use-case)

Type Example Pros Cons Use when
Offset/Page ?page=12&limit=50 or ?offset=550&limit=50 Simple; countable Slow on big tables; dupes/misses under churn; costly COUNT Small sets, admin UIs needing totals
Cursor/Keyset (seek) ?after=<opaque_cursor>&limit=50 Fast; stable under inserts/deletes; index-friendly No random access; needs stable sort keys Feeds, ledgers, logs, infinite scroll
Time-window ?from=2025-08-01T00:00Z&to=... Natural for logs/events; partition pruning Clock skew; ties on same timestamp Audit/event APIs, ingestion windows
Snapshot token ?page_token=<snapshot_id:cursor> Fully consistent page-through Snapshot/token management Reports/analytics under heavy churn
GraphQL connections edges{cursor,node{...}} pageInfo{...} Standardized cursor model Requires GraphQL infra GraphQL servers/clients

Notes: keep ordering stable (e.g., (created_at,id)), cursors are opaque (base64 of last seen keys), cap limit.

1
{ "data":[...], "page": { "next_cursor":"…", "has_more": true } }

Filtering & sorting

  • Whitelisted filters only: filter[status]=paid&filter[amount][gt]=100.
  • Allowed ops: eq, ne, gt, gte, lt, lte, in.
  • Sort: sort=-created_at,amount (include a tiebreaker like id).
  • Keep text search (q=) separate from filters; index what you expose.

Idempotency

  • Natural: PUT /resource/{id}, DELETE /resource/{id} are retry-safe.
  • POST + Idempotency-Key: client sends Idempotency-Key: <uuid>; server stores final outcome and replays the same response on retry (TTL ≥ retry window).
  • Optimistic concurrency: If-Match: <etag|version> on updates to avoid lost writes.
  • Events/Webhooks: dedupe by delivery id (inbox/outbox).
1
2
3
4
5
POST /v1/orders
Idempotency-Key: 8b8a6b38-...

201 Created
Location: /v1/orders/ord_abc123

Rate limits & quotas

  • Prefer token bucket per API key/tenant (burst-friendly). Optionally per-IP for public endpoints. Weight by cost (e.g., upload=10 tokens).
  • Communicate limits:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 123
X-RateLimit-Reset: 1723360800
  • Return 429 Too Many Requests with Retry-After.

Backpressure (stay fast under load)

  • Admission control: cap in-flight requests; reject with 503 or 429.
  • Bounded queues only; when full, shed work (don’t block forever).
  • Deadlines/timeouts end-to-end; cancel downstream when client deadline nears.
  • Bulkheads: isolate pools/queues by function/tenant.
  • Circuit breakers for flaky dependencies.
  • Async handoff for heavy jobs: 202 + operation status or webhook.

Authn/Z & tenancy

  • Authn: JWT (short-lived) or opaque tokens. Validate iss, aud, exp, nbf; rotate keys (kid, JWKS).
  • Authz: RBAC or ABAC; enforce server-side.
  • Always bind queries by tenant_id (don’t trust client-provided filters).
1
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6…

Errors & validation

  • Use problem+json; prefer 422 for validation failures.
1
2
3
4
5
6
7
{
  "type":"https://docs.example.com/errors/validation",
  "title":"Validation failed",
  "status":422,
  "errors":[{"field":"amount","msg":"must be >= 0"}],
  "instance":"req_7f1a..."
}

Caching & conditional requests

  • Support ETag/If-None-Match and/or Last-Modified/If-Modified-Since.
  • Separate public vs user-specific cache; set Cache-Control clearly.

Long-running operations & webhooks

  • Return 202 Accepted + Location: /v1/operations/{id}; allow polling.
  • Webhooks: sign (HMAC), retry with backoff, receivers must be idempotent.

OpenAPI & JSON Schema

  • OpenAPI 3.1 to describe endpoints, params, auth, errors.
  • Use JSON Schema 2020-12 for payloads; $ref shared schemas across services and docs.
  • Generate SDKs, mocks, and validation from the spec.

Choose-by-use-case (cheat sheet)

  • Create payment/booking → POST + Idempotency-Key (+ outbox).
  • Upsert config → PUT /resource/{id}.
  • Infinite feed → Cursor pagination (created_at,id).
  • Reporting export → 202 + snapshot token pagination.
  • Expensive writes → Async + bounded queue, backpressure + quotas.

Vibe-coding prompt (drop into Claude Code/Cursor)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
You are an staff software engineer at a big tech company. 
Generate production-grade HTTP APIs that follow these rules:
- Use resource-oriented design, versioned paths (/v1).
- Methods: GET/HEAD safe+cacheable; PUT/DELETE idempotent; POST uses Idempotency-Key.
- Lists: cursor pagination (stable order by created_at,id), filters (whitelisted ops eq,ne,gt,gte,lt,lte,in), sort with tiebreakers.
- Errors: RFC 9457 problem+json; use 422 for validation.
- Auth: Bearer JWT (validate iss,aud,exp,nbf), enforce RBAC/ABAC server-side; bind by tenant_id.
- Rate limits: token bucket per API key with X-RateLimit-* headers; return 429 + Retry-After.
- Backpressure: bounded queues, concurrency caps, timeouts, circuit breakers; use 202 + operation status for heavy jobs.
- Caching: ETag/If-None-Match on GET.
- Emit Request-Id and tracecontext; structured logs.

Deliver:
1) OpenAPI 3.1 spec with JSON Schemas and examples.
2) Minimal server stubs (Go/TypeScript) with middleware for request-id, JWT verify, idempotency, cursor helpers, limiter.
3) Brief README with usage and curl examples.