Skip to main content

API Conventions (REST)

Guardrails

  • JSON over HTTPS, versioned via URL prefix: /v1/...
  • Plural resources; max nesting depth 2
  • Cursor pagination: ?cursor=abc&limit=50
  • Filtering & sorting via query: ?status=active&sort=-created_at,name
  • IDs: ULIDs
  • Errors: RFC 7807 Problem+JSON
  • Auth: Firebase JWT (user) + HMAC (service‑to‑service)
  • Idempotency: Idempotency-Key header for create/charge endpoints
  • Rate limiting: token bucket; return 429 + Retry-After
  • Webhooks: HMAC-SHA256 signature + timestamp, 5 retries, 5‑minute replay window
  • Deprecation: Deprecation + Sunset headers; 90‑day policy

Express example:

import express from 'express';
import helmet from 'helmet';
import { ulid } from 'ulid';

const app = express();
app.use(helmet());
app.use(express.json());

function problem(status: number, title: string, detail?: string) {
return { type: 'about:blank', title, status, detail };
}

app.get('/v1/users', async (req, res) => {
const { cursor, limit = 50 } = req.query as { cursor?: string; limit?: string };
res.json({ data: [], nextCursor: null });
});

app.post('/v1/users', async (_req, res) => {
const id = ulid();
res.status(201).json({ id });
});

app.use((err: any, _req: any, res: any, _next: any) => {
const status = err.status || 500;
res.status(status).json(problem(status, err.title || 'Error', err.message));
});

export default app;

LLM Notes

  • Always include versioned routes. Use RFC 7807 for errors. Prefer cursor pagination.