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-Keyheader 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+Sunsetheaders; 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.