Module 04 · API fundamentals · Lesson 1 of 15
APIs as contracts
An API is a contract between a provider and its callers: what you can ask, what you get back, and what is promised about both. Why contracts outlive implementations, and the properties that make one good to depend on.
Why it matters
Every arrow between two boxes on your diagram is an API. Inside a service it is a function signature; between services it is an HTTP endpoint or a message schema; at the edge it is the thing third parties build on. Implementations are rewritten every few years, but contracts are nearly impossible to change once anyone depends on them. Designing the contract well is therefore one of the few decisions in a system with a decade-long consequence.
The idea
An API (application programming interface) defines how one piece of software asks another to do something: the operations available, the shape of inputs and outputs, the errors that can occur, and the guarantees around all of it (latency, rate limits, stability). The defining property is that the contract is separate from the implementation. A caller depends on the contract; the provider is free to change anything behind it.
What a good contract has
- 1
Clear resource and operation names
Nouns for things (orders, invoices), verbs for actions that are not CRUD (ship, refund). A reader should guess what an endpoint does from its name. - 2
Explicit schemas
Every input and output field typed and documented; optional fields marked; enumerations closed. Machine-readable where possible (OpenAPI, protobuf, GraphQL SDL) so clients and tests are generated, not hand-written. - 3
Predictable errors
A consistent error shape with a stable code, a human message and, where useful, a field path. Callers branch on codes, not on message text. - 4
Stated guarantees
Which operations are idempotent, what the rate limits are, how long results stay valid, what will never change without a new version. - 5
Backward compatibility by default
Adding fields is fine; removing or renaming them is a breaking change that needs a version. Treat the published contract as append-only.
Consumers and providers think differently
Providers want freedom to change; consumers want stability. The usual resolution is that the provider promises stability for a published contract and reserves freedom for everything behind it, communicates breaking changes through versions with a deprecation window, and never relies on consumers being well behaved (validate input, limit rate, assume retries).
| Internal API (between your services) | Public API (third parties) | |
|---|---|---|
| Change cadence | Frequent; both sides deploy together or in sequence | Rare; every change is a migration for someone else |
| Compatibility window | Days to weeks | Years, with published deprecation dates |
| Error detail | Rich, including internals | Stable codes; no internals leaked |
| Abuse assumptions | Trusted but still validated | Hostile by default: auth, quotas, input limits |
| Documentation | Schema in the repo is often enough | Reference docs, examples, changelog, sandbox |
Trade-offs
Strict contracts cost design time up front and slow down the first version; loose contracts ship faster and then freeze accidental behaviour into a de facto spec that you can never change because someone relies on it. The right amount of rigour scales with the number and distance of consumers: a contract between two teams in one repo can evolve; a contract published to ten thousand developers cannot.
In an interview
When you draw an arrow, name the contract: "The order service exposes POST /orders with an idempotency key, returns 201 with the order id, and 409 if the cart has changed." Then say how it evolves: "Additive changes only; anything breaking goes under /v2 with a six-month deprecation." Interviewers hear a person who has maintained an API, not just called one.
Check yourself
A team renames the field 'customer_id' to 'customerId' in an existing public endpoint's response 'to be consistent'. What have they done?