API Design & Contracts
An API is the menu, not the kitchen — consumers should only ever need to know what they can ask for and what they’ll get back, never how it’s cooked.
At a glance
Section titled “At a glance”- What it is: designing the request/response contract for how other systems call yours (REST, GraphQL, gRPC), plus a versioning and deprecation strategy.
- Why it matters: a stable contract lets other teams build against you with confidence; an unstable one breaks every integration whenever you refactor.
- For developers: before changing a public endpoint’s shape, ask is this a breaking change for existing consumers? — if yes, version it.
- When to use it: any time a component exposes functionality to another team, system, or external partner.
- When not to use it: internal-only functions called from the same module don’t need a formal contract — that overhead is for boundaries that cross ownership.
- Prerequisites: Service & Component Boundaries.
Mental model
Section titled “Mental model”sequenceDiagram
participant Consumer
participant API as API (v1)
participant Internal as Internal implementation
Consumer->>API: Request (contract)
API->>Internal: delegates
Internal-->>API: result
API-->>Consumer: Response (contract)
Note over API,Internal: Internal implementation can change freely as long as the contract holds
Step-by-step walkthrough
Section titled “Step-by-step walkthrough”- Define the request and response shape explicitly (fields, types, required vs. optional) before writing the implementation.
- Document what each field means and any constraints (e.g. max length, allowed values).
- Decide a versioning strategy up front (e.g. URL version, header version) — don’t wait until the first breaking change to decide.
- When a breaking change is needed, introduce a new version rather than mutating the existing contract; keep the old version live until consumers migrate.
- Communicate deprecation timelines to consumers and monitor usage of the old version before removing it.
Worked example
Section titled “Worked example”The checkout team needs to add a required field to a public Order API already used by partners.
- Input: a required
taxBreakdownfield needs to be added to the Order API response; three partner integrations currently consumev1without it. - Process: rather than mutating
v1’s response shape (which would break existing partner parsers), the team shipsv2with the new required field, documents the difference, and sets a 6-month deprecation window forv1. - Output: existing partners keep working unmodified on
v1; new and updated integrations adoptv2;v1is retired only once telemetry shows no remaining traffic.
[ApiController][Route("api/v{version:apiVersion}/orders")][ApiVersion("1.0")][ApiVersion("2.0")]public class OrdersController : ControllerBase{ [HttpGet("{id}")] [MapToApiVersion("1.0")] public ActionResult<OrderV1Response> GetV1(string id) => Ok(_orders.ToV1(id));
[HttpGet("{id}")] [MapToApiVersion("2.0")] public ActionResult<OrderV2Response> GetV2(string id) => Ok(_orders.ToV2(id)); // adds taxBreakdown}In front of the API, Azure API Management routes v1 and v2 to the same backend, applies the deprecation notice header, and reports per-version usage — the telemetry the team checks before retiring v1.
Common use cases
Section titled “Common use cases”| Use case | When to use | Notes |
|---|---|---|
| Exposing a new capability to another team | Before that team starts integrating | Define and document the contract before implementation, not after |
| Adding a required field | Only via a new version | Never make an existing required-field change on a live contract |
| Removing a deprecated endpoint | After a communicated sunset date and confirmed zero usage | Check telemetry, not assumptions, before removing |
Advanced details
Section titled “Advanced details”- Breaking vs. non-breaking changes: adding an optional field is usually safe; adding a required field, renaming a field, or changing a type is breaking.
- Contract-first design: writing the API schema (OpenAPI, GraphQL SDL, protobuf) before implementation catches shape problems before code is written around them.
- Consumer-driven contracts: for high-stakes internal APIs, consumers can supply automated tests that verify the contract they depend on, catching accidental breaks in CI.
- The Richardson Maturity Model names how RESTful an API actually is: Level 0 (a single endpoint, RPC-style over HTTP), Level 1 (separate resource URIs), Level 2 (proper HTTP verbs and status codes — where most “REST” APIs, including the Order API above, actually sit), and Level 3 (HATEOAS — responses include links to valid next actions). Most teams stop at Level 2 deliberately; Level 3 trades simplicity for discoverability that few consumers actually need.
Quick reference
Section titled “Quick reference”| Term | Meaning |
|---|---|
| Contract | The agreed request/response shape a consumer can rely on |
| Breaking change | A contract change that would fail an existing consumer |
| Versioning | Offering multiple contract versions simultaneously so consumers can migrate on their own schedule |
| Deprecation window | The communicated period during which an old version stays live before removal |
| Richardson Maturity Model | A 4-level scale (0-3) rating how RESTful an API is, from RPC-over-HTTP to full HATEOAS |
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause | Fix |
|---|---|---|
| Partner integration broke after a deploy | A required field or type changed on a live contract | Revert, and introduce the change as a new version instead |
| Consumers are scared to upgrade | No clear deprecation timeline or migration guide | Publish a sunset date and a migration guide alongside the new version |
| Old version never gets retired | No usage telemetry on the endpoint | Instrument version usage before committing to a sunset date |
Check your understanding
Section titled “Check your understanding”- Why does adding a required field count as a breaking change but adding an optional one usually doesn’t?
- Sketch the v1→v2 migration timeline you’d propose for the Order API example.
Related pages
Section titled “Related pages”Part of Introduction to Software & Application Architecture. See also Service & Component Boundaries, Event-Driven & Asynchronous Communication, and the Glossary.