Unit 2 · Software engineering · Lesson 2 of 15
API contracts and compatibility
Step 1: Learn the ideas
- Resource-oriented design and naming
- Request validation and structured errors
- Offset, keyset and opaque-cursor pagination
- Pagination consistency under concurrent writes
- Idempotency keys and safe retries
- Optimistic concurrency with ETags and If-Match
- Partial updates and field masks
- Backward and forward compatibility
- Versioning, deprecation and sunset policy
- Long-running and asynchronous operations
Why it matters
Once another team, a mobile app or a customer integration calls your endpoint, its observable behavior becomes the contract, including details you never meant to promise. Changing it later means coordinating with every client, so mistakes made in the first design review are expensive. API questions, and the API step of system design rounds, reveal whether you think about failure (what happens when the client times out and retries?), concurrency (what does page three mean while rows are being inserted?) and evolution (how do you add a field next quarter?), not only about URL naming.
Core ideas
Contracts and validation. Model resources and the operations on them, make required fields, formats, ranges and limits explicit, and validate at the boundary so bad input fails fast with a precise message. Return errors in one structured shape: an HTTP status for the class of failure, a stable machine-readable code, a human-readable message and field-level details. RFC 9457 (Problem Details) and Google's AIP-193 are two documented conventions. Never leak stack traces or internal identifiers.
Pagination. Offset pagination (LIMIT 20 OFFSET 40) is simple and allows jumping to page N, but the database still walks the skipped rows, and a row inserted before the current position makes the client see a duplicate while a deletion makes it skip one. Keyset or cursor pagination remembers the last sort key returned and asks for rows after it, for example WHERE (created_at, id) > ($1, $2) ORDER BY created_at, id LIMIT 20. It stays fast on deep pages and does not shift when earlier rows change, but it needs a unique, stable ordering (add the primary key as a tie-breaker) and cannot jump to arbitrary pages. Neither scheme is a snapshot: a row updated after you passed it is not revisited. If clients need a consistent view, page over a read timestamp or a materialized result. Wrap the cursor in an opaque token so its internals can change, reject tokens used with different filters, and cap the page size.
Idempotency. Networks fail after the server commits but before the client hears back, so clients retry. A POST that creates a payment is not idempotent, so the standard fix is a client-generated idempotency key per logical operation. The server stores the key with a fingerprint of the request and the eventual response; a repeat with the same key returns the stored result instead of acting again, and a repeat with a different payload is rejected. Claim the key with a uniqueness constraint so two concurrent duplicates cannot both proceed, mark it in progress until the work finishes, and where possible commit it in the same transaction as the side effect. Keep keys longer than any client's retry horizon.
Concurrency control. Lost updates happen when two clients read, modify and write the same resource. Return an ETag (a version) and require If-Match on updates; the server answers 412 Precondition Failed if the version moved, and the client re-reads and retries deliberately.
Compatibility and versioning. Backward compatibility means new servers still serve old clients; forward compatibility means old readers tolerate data or responses produced by newer writers, usually by ignoring unknown fields. Adding an optional field or a new endpoint is normally safe. Removing or renaming fields, changing types or units, tightening validation, adding a required request field, or changing defaults and error codes breaks someone. During a rolling deployment old and new server versions run side by side, so even internal APIs need compatibility across adjacent versions. Reserve new major versions for unavoidable breaks, announce deprecations with dates, and measure who still calls the old version before removing it.
Common misconceptions
- "PUT and DELETE are idempotent, so retries are always harmless." The intended effect repeats safely, but responses can differ (a second DELETE may return 404), and a blind retried PUT can overwrite a newer write unless it carries a precondition.
- "Cursor pagination gives a consistent snapshot." It prevents shifting pages, but concurrent updates can still move rows across the cursor or change their content.
- "An idempotency key is just a deduplication cache." Without an atomic claim, two concurrent requests with the same key can both execute; without a payload fingerprint, a buggy client can reuse a key for a different operation.
- "Adding an enum value can never break anyone." Clients that switch exhaustively over the values may fail on an unknown one; document that enums can grow and make clients handle unknown values.
- "Versioning solves compatibility." Every live version is code to run, test and secure; additive design avoids most new versions.
How to approach it
Read Google's AIPs on pagination, request identification, errors and backwards compatibility, and Stripe's API reference on idempotent requests as a worked example of a retry contract. Then write a one-page spec for a small resource you know, such as outreach campaigns with messages: list and get, create with an idempotency key, update with a precondition, and an error table. In interviews, state the contract before the implementation, name the retry and pagination semantics explicitly, and offer a compatibility plan for the first change you expect. Graduate candidates are expected to get methods, status codes and pagination right; mid-level candidates should handle idempotency and conflicts; senior candidates are pushed on evolving an API across many clients and on how the guarantees hold during deployments.
Suggested order
- 1Read AIP-121 (Resource-oriented design) and AIP-193 (Errors), then RFC 9457, and compare the two error models.
- 2Read AIP-158 (Pagination); then, on paper, simulate offset and keyset pagination over a table while inserting and deleting rows between page requests, and record which rows are skipped or duplicated.
- 3Read Stripe’s API reference on idempotent requests (and its linked background article), then AIP-155 (Request identification); design the idempotency-key table and its states, including concurrent duplicates and mismatched payloads.
- 4Read AIP-154 (Resource freshness validation) and explain how ETags with If-Match prevent lost updates.
- 5Read AIP-180 (Backwards compatibility) and AIP-185 (API Versioning), then DDIA Ch. 4 (Encoding and Evolution) for backward and forward compatibility of stored data.
- 6Read the Protocol Buffers rules for updating a message type; explain why every reader must understand or safely ignore a new field before any writer emits it, and why removed field numbers are reserved.
- 7Write a one-page API spec for a small resource, including an error table and three expected future changes classified as compatible or breaking.
Step 2: Check off what you can do
Tap each one you can genuinely do or explain. Each earns 10 XP.
Step 3: Go deeper
Google API Improvement Proposals (AIPs)Start here
GoogleSection: AIP-158: Pagination
Short, opinionated guidance on resource design, pagination, request identifiers, errors, freshness checks, compatibility and versioning; read the listed AIPs as design checklists.
Open(opens in a new tab)Link responded, 5 Oct 2026Stripe API reference: Idempotent requestsStart here
Stripe documentationA real API’s idempotency contract (key format, retention, which results are replayed, rejection of reused keys with different parameters) to compare against your own design.
Open(opens in a new tab)Link responded, 5 Oct 2026Designing Data-Intensive Applications
O'Reilly Media (author's book site)Section: Ch. 4: Encoding and Evolution
Backward and forward compatibility of data formats and schemas, and why rolling upgrades need both.
Open(opens in a new tab)Link responded, 5 Oct 2026RFC 9457: Problem Details for HTTP APIs
IETF (RFC Editor)A standard shape for machine-readable HTTP API errors that you can propose or compare against when designing an error model.
Open(opens in a new tab)Link responded, 5 Oct 2026Protocol Buffers Language Guide (proto 3)
Protocol Buffers documentation (Google)Section: Updating A Message Type
Concrete rules for which schema changes old and new readers survive, including never reusing field numbers and preserving unknown fields.
Open(opens in a new tab)Link responded, 5 Oct 2026
Step 4: Practice
Write a spec, not code: pick a small resource with a list endpoint and a non-idempotent create, and specify schemas, validation, errors, pagination, idempotency and conditional updates. Then attack your own design: replay every request twice, interleave two clients, insert rows between page fetches, and propose a field rename to see what breaks. For a coding variant, implement the idempotency-key table in PostgreSQL and send concurrent duplicates from two sessions.
No external problems are linked; follow the guidance above.
Record attempts on each problem’s page (+15 XP, +10 more when solved on your own).
Step 5: Test yourself
Answer each one out loud or on paper before you finish the lesson.
- Question 1What does a client see with offset pagination if a row on an earlier page is deleted between requests?
- Question 2Why must the pagination cursor include a unique column, and what goes wrong without it?
- Question 3What should a server do when an idempotency key is reused with a different request body?
- Question 4Which header and status code implement optimistic concurrency for updates?
- Question 5Why do internal APIs still need backward compatibility during a rolling deployment?
- Question 6When is a new major API version justified, and what does it cost?
You’re done when you can
- Write a keyset pagination query with a unique tie-breaker from memory and explain what clients see when rows are inserted mid-iteration.
- Explain the full idempotency-key flow, including concurrent duplicates and mismatched payloads, in under three minutes.
- Classify ten proposed API changes as compatible or breaking with a one-line reason each.
- Produce a one-page API spec that another engineer could implement without asking about errors, pagination or retries.
Finish the lesson
Unlocks next: Deployment, observability, rollout and schema migration, Frontend state, request races and client-server interfaces, System design method, AI workflows, agents and tool contracts


