Article
Writing better API contracts
Shape payloads for clarity, evolution, and fewer surprises across teams.
- APIs
- TypeScript
- DX
APIs harden the moment they leave your editor. The best contracts read like product copy: obvious names, predictable shapes, and room to grow without breaking clients.
Start with the consumer
Before debating frameworks, write the call site you wish existed. If the first integration feels verbose, your contract is still too clever.
Name fields for intent
Prefer status over s, createdAt over ts, and verbs in route names only when they describe
actions—not resources.
Version deliberately
Small additive changes rarely need a /v2. Breaking changes deserve a migration guide, not a
surprise diff. Pair OpenAPI or JSON Schema with examples that mirror production traffic.
type CreateInvoiceInput = {
customerId: string;
currency: "USD" | "EUR" | "GBP";
lineItems: { description: string; amountCents: number }[];
};
type CreateInvoiceResult =
| { ok: true; invoiceId: string }
| { ok: false; error: "invalid_customer" | "currency_locked" };Close the loop
Ship a changelog, annotate deprecations with dates, and measure client errors after each release. Contracts are conversations—keep them respectful, explicit, and kind to the next person integrating.