BackendAPI designRESTGraphQL

Designing APIs That Don't Break Your Clients

Versioning policy, idempotency keys, and contract tests — the three habits that keep integrators shipping while you evolve.

Principal Engineer · · Updated · 9 min read

XLinkedIn

Most API breakage is not a technical accident — it is a governance failure. A field gets renamed on a Friday, a status code drifts from 422 to 400, and three integrators wake up to red dashboards. The fix is boring on purpose: version explicitly, make retries safe, and test the contract in CI.

1. Version like you mean it

URL versioning (/v1/charges) wins for public APIs because it is visible in logs, caches, and docs. Never remove a version without a sunset header, a changelog entry, and a migration window measured in months. Stripe's discipline here is the benchmark: old versions keep working while new ones ship.

  • Additive changes only inside a major version — new optional fields are fine, renames are not
  • Return Sunset + Deprecation headers at least 180 days before removal
  • Keep a per-version changelog with before/after payloads, not prose
  • Gate breaking changes behind preview headers before promoting them

2. Make every mutation retry-safe

Networks fail mid-request. Without idempotency keys, a retried POST becomes a double charge. Require Idempotency-Key on all mutations, store the keyed response for 24–72 hours, and return the original response on replay with a replayed: true signal for observability.

Idempotent charge creationhttp
POST /v1/charges HTTP/1.1
Idempotency-Key: 7f3a-9c2e-41bd
Content-Type: application/json

{
  "amount": 4200,
  "currency": "usd",
  "customer": "cus_9H2K"
}

3. Test the contract, not just the code

Unit tests prove your handler works. Contract tests prove your promise holds. Publish an OpenAPI or GraphQL schema from code (never hand-edit the artifact), then run consumer-driven pact tests and breaking-change detectors in CI. If CI is green but the diff renames a field, the pipeline should fail.

“A stable API is a product feature. Every silent breaking change is a churn event you will never see in analytics. — APHYP API review checklist”

A checklist you can steal

  • OAuth scopes per resource, key rotation runbook, per-tenant rate limits with RateLimit headers
  • Problem+JSON errors with type, title, detail, and traceable instance IDs
  • Cursor pagination with opaque cursors — never OFFSET on growing tables
  • Postman collection + reference docs generated on every merge to main

Questions, answered

Should I version with URLs or headers?

URL versions (/v1, /v2) are explicit and easy to route, cache, and document. Reserve header versioning for internal platforms where gateway control is absolute. Either way, publish a deprecation policy with dates, not vibes.

When does GraphQL beat REST?

When clients need shaped, aggregated reads across domains and your team can fund schema governance, persisted queries, and per-field observability. Otherwise well-designed REST with sparse fieldsets ships faster.