Skip to content
← Back to blog

Article

Writing better API contracts

Shape payloads for clarity, evolution, and fewer surprises across teams.

1 min read
  • 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.