SD System Design In Depth
0%
lesson system-designapicontracts

API design contracts

Module 3.1: Network Protocols & Service Communication Updated Sep 17, 2026
📄 Cheat Sheet
The principles behind great API design | Stripe Sessions 2019
Stripe The principles behind great API design | Stripe Sessions 2019 Watch on YouTube ↗

Stripe, whose API is widely treated as the reference for evolvable public contracts, explains how they design resources, keep backward compatibility and version without breaking existing integrations.

0:00 / --:--

Under the Hood: The API as a Long-Lived Network Contract

An API contract is not merely a collection of JSON payloads and HTTP routes; it is a long-lived interface boundary between independent deployment lifecycles. It can evolve through additive, backward-compatible changes, but existing behaviour must not break. In distributed systems, breaking changes deployed to an API immediately cascade into client crashes, silent data corruption, or catastrophic transaction drops.

The Life of an API Contract Across Lifecycles

flowchart TD ClientApp["Mobile / Browser Client (v1.2.0)"] -->|"POST /v1/orders (Strict Schema)"| APIGateway["API Gateway & Validation Filter"] APIGateway -->|"Validate Payload against JSONSchema / Protobuf"| SchemaCheck{"Valid Schema?"} SchemaCheck -->|"No"| Err400["HTTP 400 Bad Request (RFC 9457 Problem Details)"] SchemaCheck -->|"Yes"| Route["Route to Service (gRPC / HTTP2)"] Route --> Service["Order Processing Engine"] Service -->|"Idempotent Execution with Lock"| DB[("Orders DB")]

Core Elements of Production-Grade API Contracts

1. Idempotency Key Semantics

Network timeouts leave callers in an ambiguous state: did the server crash before executing the mutation, or was the response lost in flight? Non-idempotent mutating endpoints (typically POST, and PATCH when it is not naturally idempotent) should accept an Idempotency-Key header, an IETF draft (draft-ietf-httpapi-idempotency-key-header) popularised by Stripe. The server records the key alongside the HTTP response within a single atomic database transaction. Retried requests with the identical key and a matching request body bypass business logic and replay the cached HTTP response; reusing a key with a different body is rejected as an error.

2. Backward Compatibility & Field Deprecation

Never rename or change the type of an existing field. Follow the robustness principle (Postel's law): producers should be conservative in what they send, and consumers liberal in what they accept. The consumer side of this is the Tolerant Reader pattern: ignore unknown fields and read only what you need. New fields must be optional. Deprecated fields must remain populated through formal sunset timelines communicated via Sunset: <date> (RFC 8594) headers.

3. Standardized Error Handling (RFC 9457 Problem Details)

APIs should return uniform machine-readable error responses containing a type URI, human-readable title, HTTP status, granular detail, and a correlation instance trace ID to eliminate ambiguous ad-hoc client parsing.

Worked example: evolving a create-link endpoint

Version 1 of the shortener API is POST /v1/links with body {"url": "https://example.com/a"} returning 201 Created and {"code": "Ab3xY9", "url": "..."}.

Safe change: add an optional expires_at request field and a new created_at response field. Old clients ignore created_at (if they are tolerant readers) and never send expires_at, so their behaviour is unchanged.

Breaking changes: renaming url to destination, making expires_at required, changing the code length that clients validate with a regex, or returning 200 instead of 201. Each needs either a new version or a migration period where both forms are accepted.

Retry safety: the client sends Idempotency-Key: 5f2c.... The server stores the key, a hash of the request body and the final response in the same transaction as the link insert. A retry with the same key and body returns the stored response; the same key with a different body returns an error (for example 422) so accidental key reuse is detected. Keys expire after a retention window, for example 24 hours at Stripe.

Compatibility rules at a glance

ChangeUsually safe?Notes
Add optional request fieldYesServer must supply a sensible default
Add response fieldYes, for tolerant clientsStrict deserializers that reject unknown fields will break
Add new enum valueRiskyClients with exhaustive switch statements may crash; document that enums can grow
Remove or rename fieldNoDeprecate, keep populating, announce with a Sunset header, then remove in a new version
Make optional field requiredNoOld clients never send it
Change field type or unitsNoAdd a new field instead
Tighten validationNoPreviously accepted requests start failing

Standard building blocks

  • Errors: RFC 9457 Problem Details (which obsoletes RFC 7807) defines a JSON error body with type, title, status, detail and instance.
  • Deprecation: the Sunset header (RFC 8594) announces when a resource will stop working; the Deprecation header (RFC 9745) signals that it is deprecated now.
  • Idempotency: the Idempotency-Key header is an IETF draft, popularised by Stripe; many providers implement it.
  • Versioning: URL versions (/v1) are simple and visible; Stripe uses dated versions pinned per account with a request header to override, translating responses between versions internally.

Failure modes

  • Idempotency record written outside the business transaction: a crash between the two lets a retry perform the action twice.
  • Concurrent retries with the same key both running: lock on the key or insert it first with an in-progress status.
  • Deprecation announced but never measured: log which clients still call old fields before removing them.
Check yourself Question 1 of 3
Reused idempotency key recall
From: Core Elements of Production-Grade API Contracts

A client retries POST /v1/links with the same Idempotency-Key as an earlier request, but the request body is different this time. What should the server do?

Key Takeaways

API contracts are long-lived boundaries that evolve additively; changing an existing field breaks older mobile and third-party clients.
Idempotency keys allow safe client retries by caching transaction outcomes and replaying responses.
Use RFC 9457 Problem Details and RFC 8594 Sunset headers for predictable error handling and deprecations.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue