Skip to content

Latest commit

 

History

History
406 lines (336 loc) · 15.5 KB

File metadata and controls

406 lines (336 loc) · 15.5 KB

REST API Guideline

This document is an actionable, LLM-friendly playbook for building consistent, evolvable REST APIs

Table of Contents

Core Framework

Advanced Patterns

Implementation Details

Reference

Quick Reference

1. Core Principles

  • Consistency over novelty: Prefer one clear way to do things.
  • Explicitness: Always specify types, units, timezones, and defaults.
  • Evolvability: Versioned paths, idempotency, and forward-compatible schemas.
  • Observability: Every request traceable end-to-end.
  • Security first: HTTPS only, least privilege, safe defaults.

2. Protocol & Content

  • Media type: application/json; charset=utf-8 (request & response)
  • Errors: Problem Details application/problem+json (RFC 9457)
  • Encoding: UTF-8
  • Compression: gzip/br when client sends Accept-Encoding
  • Idempotency: Idempotency-Key header on unsafe methods (see §7)

3. Resource Modeling & URLs

  • Nouns, plural: /users, /tickets, /tickets/{ticket_id}
  • Hierarchy if strict ownership: /users/{user_id}/keys
  • Prefer top-level + filters over deep nesting: /tickets?assignee_id=...
  • Identifiers: uuidv7 (or ulid). JSON field: id
  • Timestamps: ISO-8601 UTC with Z, always include milliseconds .SSS (e.g., 2025-09-01T20:00:00.000Z)
  • Standard fields: created_at, updated_at, optional deleted_at

4. JSON Conventions

  • Naming: snake_case (consistent with backend conventions and databases)
  • Nullability: Prefer omitting absent fields over null
  • Booleans & enums: Strongly typed; never stringly booleans
  • Money: Integer minor units + currency code
  • Lists: Arrays; use [] not null
  • Envelope:
    • Lists: Use items array with optional top-level page_info for pagination
    • Single objects: Return fields directly at top level (no wrapper)
// List response
{
  "items": [ /* array of objects */ ],
  "page_info": { /* optional: limit, next_cursor, prev_cursor */ }
}

// Single object response (no wrapper)
{
  "id": "01J...",
  "title": "Example",
  "created_at": "2025-09-01T20:00:00.000Z"
}

5. Pagination, Filtering, Sorting, Field Projection

For complete specification see QUERYING.md:

  • Cursor pagination: Opaque, versioned cursors with limit and cursor parameters
  • Filtering: OData $filter with operators (eq, ne, gt, in, etc.)
  • Sorting: OData $orderby (e.g., priority desc,created_at asc)
  • Field projection: OData $select for sparse field selection (e.g., $select=id,title,status)

6. Request Semantics

  • Create: POST /tickets → 201 + Location + resource in body
  • Partial update: PATCH /tickets/{id} (JSON Merge Patch)
  • Replace: PUT /tickets/{id} (complete representation)
  • Delete: DELETE /tickets/{id} → 204; if soft-delete, return 200 with deleted_at

7. Error Model (Problem Details)

  • Always return RFC 9457 Problem Details for 4xx/5xx
{
  "type": "https://api.example.com/errors/validation",
  "title": "Invalid request",
  "status": 422,
  "detail": "email is invalid",
  "instance": "https://api.example.com/req/01J...Z",
  "errors": [
    { "field": "email", "code": "format", "message": "must be a valid email" }
  ],
  "trace_id": "01J...Z"
}
  • Mappings: 422 (validation), 401/403 (authz), 404, 409 (conflict), 429, 5xx (no internals)

8. Concurrency & Idempotency

  • Optimistic locking: Representations carry ETag (strong or weak). Clients send If-Match. On mismatch → 412.
  • Idempotency: Clients may send Idempotency-Key on POST/PATCH/DELETE.
    • Server caches only successful (2xx) responses to prevent duplicate side effects.
    • Error responses (4xx/5xx) are NOT cached; retries re-execute to allow fresh validation, permission checks, and recovery from transient failures.
    • Successful replays return the cached response with header: Idempotency-Replayed: true.
    • Retention tiers:
      • Minimum default: 1 hour (sufficient for network retry protection)
      • Important operations: 24h-7d (e.g., notifications, reports) - must be documented per endpoint
      • Critical operations: Permanent via DB uniqueness constraints (e.g., payments, user registration) → return 409 Conflict with existing resource after initial creation

9. Authentication & Authorization

  • Auth: OAuth2/OIDC Bearer tokens in Authorization: Bearer <token>
  • Scopes/permissions: Document per endpoint; insufficient → 403
  • Service-to-service: mTLS optional
  • No secrets in URLs; short token TTLs; rotate keys; refresh tokens when needed

10. Rate Limiting & Quotas

  • Headers (following IETF RateLimit Draft):
    • RateLimit-Policy: "default";q=100;w=3600 (defines quota policy: 100 requests per hour)
    • RateLimit: "default";r=72;t=1800 (current status: 72 remaining, resets in 1800 seconds)
  • On 429 also include Retry-After (seconds). Quotas are per token by default.
  • Example with partition key: RateLimit-Policy: "peruser";q=100;w=60;pk=:dXNlcjEyMw==:
  • Backward compatibility: For legacy clients, servers MAY also return traditional X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers alongside the standard headers during a transition period.
  • Note: The IETF draft uses structured field syntax with parameters (not separate headers). Many existing APIs use X-RateLimit-* headers, which remains acceptable for backward compatibility.

11. Asynchronous Operations

  • For long tasks return 202 Accepted + Location: /jobs/{job_id}
  • Job resource example:
{
  "id": "01J...",
  "status": "queued|running|succeeded|failed|canceled",
  "percent": 35,
  "result": {},
  "error": {},
  "created_at": "...",
  "updated_at": "..."
}
  • Clients poll GET /jobs/{id} or subscribe via SSE/WebSocket if available

12. Webhooks (Outbound)

  • Event shape: event_type, id, created_at, data
  • Delivery: POST JSON to subscriber URL
  • Security: X-Signature HMAC-SHA256 over raw body with shared secret; include X-Timestamp (±5 min skew)
  • Retries: Exponential backoff for ≥24h; dead-letter queue
  • Idempotency: Include event_id; receivers dedupe

13. Internationalization, Numbers & Time

  • All timestamps UTC (Z), always include milliseconds .SSS (e.g., 2025-09-01T20:00:00.000Z). If timezone needed, add separate timezone (IANA name)
  • JSON numbers for typical values; use strings for high-precision decimals or use integer minor units
  • Sorting/filters are locale-agnostic unless documented otherwise
  • Errors are not localized; localization is handled by the UI/API client.

14. Caching

  • Reads: ETag + Cache-Control: private, max-age=30 when safe
  • Mutations: Cache-Control: no-store
  • Conditional: If-None-Match → 304

15. Security & CORS

  • HTTPS only; HSTS enabled
  • CORS allow-list explicit origins; example:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET,POST,PUT,PATCH,DELETE,OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, Idempotency-Key
Access-Control-Expose-Headers: ETag, Location, RateLimit, RateLimit-Policy
  • CSRF: only relevant for cookie auth; prefer Bearer in Authorization for SPAs
  • Content Security Policy on the app domain; avoid wildcard */*

16. Observability & Diagnostics

  • Tracing: accept/propagate traceparent (W3C). Emit trace_id header on all responses
  • Request ID: honor X-Request-Id or generate one
  • Structured logs: JSON per request: trace_id, request_id, user_id, path, status, duration_ms, bytes
  • Metrics: RED/USE per route, with p50/p90/p99

17. Versioning & Deprecation

  • Path versioning: /v1 (breaking changes bump major)
  • Non-breaking changes: Adding optional fields/params, new endpoints, new enum values, relaxing validation
  • Breaking changes: Removing fields, changing types/semantics, making optional fields required, changing URL structure
  • Client compatibility: Must ignore unknown fields, handle new enum values gracefully, not rely on field order
  • Deprecation headers: Deprecation: true, Sunset: <RFC 8594 date>, and Link: <doc>; rel="deprecation"

18. Canonical Status Codes

For complete HTTP status code definitions and application error mappings, see STATUS_CODES.md.

Quick reference:

  • 200 OK (read/update)
  • 201 Created (+ Location)
  • 202 Accepted (async)
  • 204 No Content (delete or idempotent update without body)
  • 400 Bad Request
  • 401 Unauthorized / 403 Forbidden
  • 404 Not Found
  • 409 Conflict
  • 410 Gone (for deprecated endpoints)
  • 412 Precondition Failed (ETag)
  • 415 Unsupported Media Type
  • 422 Unprocessable Entity (validation)
  • 429 Too Many Requests
  • 503 Service temporarily overloaded or under maintenance
  • 5xx Other Server errors

19. Batch & Bulk

For complete batch and bulk operations specification including error formats, atomicity options, and idempotency, see BATCH.md.

Quick Summary:

  • Endpoint pattern: POST /resources:batch (default maximum 100 items, configurable per endpoint)
  • Response: 207 Multi-Status (partial success) or specific status code (all same outcome)
  • Error format: Full RFC 9457 Problem Details per failed item
  • Atomicity: Endpoint-specific (best-effort default, atomic for critical operations)
  • Idempotency: Per-item idempotency_key with 1-hour retention
  • Optimistic locking: Per-item if_match field for version checking

20. OpenAPI & Codegen

  • Source of truth: OpenAPI 3.1
  • For Rust backend specifics (utoipa, serde, validator), see RUST.md
  • Client SDK: generate TypeScript types (openapi-typescript) and React hooks (TanStack Query) with fetch/axios adapter
  • Keep schemas DRY via shared components; provide example payloads for every operation

21. Example Endpoints

  • List Tickets
curl -sS \
  -H "Authorization: Bearer $TOKEN" \
  "https://api.example.com/v1/tickets?limit=25&cursor=...&\$filter=status in ('open','in_progress')&\$orderby=priority desc,created_at asc&\$select=id,title,priority,status,created_at"
{
  "items": [
    { "id": "01J...", "title": "Disk full", "priority": "high", "status": "open", "created_at": "2025-08-31T10:05:17.000Z" }
  ],
  "page_info": {
    "limit": 25,
    "next_cursor": "eyJ2IjoxLCJrIjpbIjIwMjUtMDgtMzFUMTA6MDU6MTcuMDAwWiIsIjAxSi4uLiJdLCJvIjoiZGVzYyIsInMiOiJjcmVhdGVkX2F0LGlkIn0",
    "prev_cursor": null
  }
}
  • Update with Concurrency
PATCH /v1/tickets/01J...
If-Match: W/"etag-abc"
Idempotency-Key: 5b2f...

{ "status": "in_progress" }
  • Async Job
POST /v1/reports → 202 Accepted
Location: /v1/jobs/01J...

22. Backward Compatibility Rules (Client-facing)

  • Clients ignore unknown fields
  • Do not rely on property order
  • Treat enums laxly: unknown enum → display as string, never crash
  • Handle pagination cursors generically

23. Performance & DoS

  • Enforce max list limits and payload sizes (e.g., 1MB JSON)
  • Deny N+1 by default; allow explicit include= with documented caps
  • Timeouts: handler ≤ 30s; use async jobs for longer work
  • Strict input validation with precise 422s

24. Documentation Style

Each endpoint must be comprehensively documented to serve both human developers and AI assistants.

Required Documentation Elements

  1. Summary & Description

    • One-line summary
    • Detailed purpose explanation
    • When to use this endpoint
  2. Authentication & Authorization

    • Required authentication method
    • Required scopes/permissions
  3. Request Specification

    • HTTP method and path
    • Path parameters (type, format, constraints)
    • Query parameters (defaults, validation)
    • Request headers (required and optional)
    • Request body schema with field descriptions
  4. Response Specification

    • Success status codes
    • Response headers
    • Response body schema
    • Example successful responses
  5. Error Documentation

    • All possible error status codes
    • Problem Details examples for each
    • Common error scenarios
  6. Rate Limiting

    • Rate limit class
    • Quota consumption
  7. Code Examples

    • curl with realistic data
    • TypeScript with generated client

Documentation Template

Endpoint: POST /v1/resources Purpose: Create new resource Authentication: Required (OAuth2) Authorization: resources:write scope Rate Limit: Standard (100/hour)

Request Body Schema:

{
  "title": "string (required, max 255 chars)",
  "description": "string (optional, max 1000 chars)",
  "priority": "enum: low|medium|high",
  "category": "string (optional)"
}

Success Response (201):

{
  "id": "res_01JCXYZ...",
  "title": "string",
  "description": "string",
  "priority": "medium",
  "category": "string",
  "status": "active",
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T10:30:00Z"
}

Error Responses:

  • 400: Invalid request body
  • 401: Missing/invalid authentication
  • 403: Insufficient permissions
  • 422: Validation errors (Problem Details)
  • 429: Rate limit exceeded

Code Examples:

curl -X POST https://api.example.com/v1/resources \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{"title": "Example", "priority": "medium"}'
const resource = await api.createResource({
  title: 'Example',
  priority: 'medium'
});

Operational Headers (Quick Reference)

  • Requests may include: Authorization, Idempotency-Key, If-Match, If-None-Match, Accept-Encoding, traceparent, X-Request-Id
  • Responses should include: Content-Type, ETag (when cacheable), Location (201/202), RateLimit, RateLimit-Policy, traceId, X-Request-Id

References