API Design Principles

Guidelines for building consistent, versioned, and well-documented REST and GraphQL APIs that are easy to consume, evolve, and operate. These conventions follow the Microsoft REST API Guidelines, Google AIPs, and the JSON:API specification where applicable.

Resource Modeling

  • Use nouns, not verbs for resources (/users/123/orders, not /getUserOrders?id=123).
  • Pluralize collection names; identify items by stable IDs (preferably ULID/UUIDv7).
  • Express relationships hierarchically only when one resource cannot exist without the other.
  • Use kebab-case in URLs and camelCase in JSON bodies.

HTTP Semantics

  • GET safe & idempotent · PUT/DELETE idempotent · POST for creation and non-idempotent actions · PATCH for partial updates (RFC 7396 or JSON Patch RFC 6902).
  • Return correct status codes: 200, 201, 202, 204, 400, 401, 403, 404, 409, 422, 429, 5xx.
  • Honor conditional requests with ETag / If-Match for optimistic concurrency.
  • Support an Idempotency-Key header on unsafe operations to make retries safe.

Versioning

Version on the URL path (/api/v1/...). Breaking changes require a new major version; additive changes ship in place. Deprecate with a Sunset response header and an entry in the changelog.

Request & Response Envelope

Use a consistent shape: data for the payload, error for failures (RFC 9457 Problem Details), and optional meta for pagination, links, and request IDs.

{
  "data": { "id": "01H...", "name": "Acme" },
  "meta": {
    "requestId": "req_01H...",
    "page": { "next": "/api/v1/orgs?cursor=abc" }
  }
}

Error Model (RFC 9457)

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json

{
  "type": "https://errors.example.com/validation",
  "title": "Invalid request",
  "status": 422,
  "detail": "name must be at least 2 characters",
  "instance": "/api/v1/orgs",
  "errors": [{ "field": "name", "code": "min_length" }]
}

Pagination, Filtering, Sorting

  • Prefer cursor-based pagination for stable iteration (?cursor=...&limit=50).
  • Filtering: ?status=active&createdAfter=2025-01-01.
  • Sorting: ?sort=-createdAt,name (leading - for descending).

Authentication & Authorization

Use OAuth 2.1 / OpenID Connect with short-lived access tokens (JWT) and rotating refresh tokens. Authorize at the resource level with scopes and, where needed, fine-grained permissions (RBAC or ABAC). Never log bearer tokens or PII.

OAuth 2.1 Authorization Code with PKCE.

Reliability

  • Rate limiting with X-RateLimit-Limit/Remaining/Reset and 429 responses including Retry-After.
  • Timeouts on every outbound call; retries with exponential backoff and jitter; circuit breakers for sustained failures.
  • Idempotency on writes — store the response keyed by the idempotency key for at least 24 hours.

Documentation & Contracts

  • Every public endpoint has an OpenAPI 3.1 definition with at least one example for each response.
  • Contract-test consumers and providers in CI (e.g., Pact).
  • Publish a machine-readable changelog and a versioned reference site.

GraphQL Notes

  • Avoid the N+1 problem with DataLoader-style batching; persist queries in production.
  • Use Relay-style cursor pagination (edges, pageInfo).
  • Enforce query depth, complexity, and rate limits at the gateway.