Designing APIs That Don't Break When You Change Them

The hardest problem in API design is not building the first version. It is changing it without breaking everyone who built on it. Every successful API eventually needs to evolve: new features, better

The hardest problem in API design is not building the first version. It is changing it without breaking everyone who built on it. Every successful API eventually needs to evolve: new features, better patterns, corrected mistakes. The question is whether that evolution breaks existing integrations or extends them gracefully.

Additive Changes Are Free

The simplest rule for backward compatibility: you can always add, but you can rarely remove. Adding a new field to a response is safe because well-written clients ignore unknown fields. Adding a new optional parameter to a request is safe because existing requests do not include it. Adding a new endpoint is safe because nobody is calling it yet.

This means the most important decision in API design is what to include in version one. Every field you include is a commitment. Every endpoint you publish is a contract. Be conservative in your first release and generous in subsequent additions.

The Versioning Decision

URL-based versioning (/api/v1/, /api/v2/) is the most common approach and the easiest for developers to understand. When you need to make breaking changes, you release a new version and keep the old one running. Developers migrate at their own pace.

Header-based versioning (Accept: application/vnd.api+json;version=2) is more "RESTful" but harder to discover, test, and debug. Query parameter versioning (?version=2) is a middle ground that is easy to use but clutters URLs.

Our recommendation: URL-based versioning. It is visible, cacheable, and obvious. Every one of our products — DocuMint, CronPing, FlagBit, WebhookVault — uses /api/v1/ paths.

Cursor-Based Pagination

Offset-based pagination (?page=3&per_page=20) seems natural but breaks under concurrent modifications. If a new record is inserted while a client is paginating, they either skip a record or see one twice. Cursor-based pagination (?after=eyJpZCI6MTIzfQ==) solves this by anchoring the query to a specific position in the dataset.

The cursor is typically an opaque, base64-encoded identifier that your server decodes to construct the next database query. Clients do not need to understand the cursor format — they just pass back whatever you gave them.

Deprecation Done Right

When you need to remove or change something, the deprecation process matters as much as the change itself:

  1. Announce early. Add a Deprecation header and Sunset header to responses from the deprecated endpoint. These are real HTTP standards (RFC 8594, RFC 8977).
  2. Provide migration guides. Do not just say "v1 is deprecated." Show exactly what the v2 equivalent is for every v1 endpoint.
  3. Monitor usage. Track how many requests still hit deprecated endpoints. Do not remove them until traffic drops to near zero.
  4. Set a deadline. "Eventually" is not a timeline. Give a specific date, at least 6 months out, and communicate it in API responses, documentation, and email.

Idempotency by Default

Network requests fail and clients retry. If your POST endpoint creates a new resource every time it receives the same request, you get duplicates. The solution is idempotency keys: the client sends a unique identifier (typically a UUID) with each request. If the server has already processed a request with that key, it returns the original response instead of creating a duplicate.

curl -X POST https://api.example.com/v1/invoices   -H "Authorization: Bearer sk_..."   -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000"   -d '{"amount": 9900, "currency": "usd"}'

Store idempotency keys with their responses for at least 24 hours. Return the stored response on duplicate requests with the same key. This turns a scary POST into a safe, retryable operation.

The Cost of Getting It Wrong

Every breaking change costs trust. Developers who integrate with your API are making a bet that it will be stable. Every time you break that bet, they remember. Some of them will not come back.

The goal is not a perfect API from day one. The goal is an API that can evolve without punishing the people who trusted it early.

Read more