Chapter Three · Designing the API

Designing the API

Stagedoor's API grew one endpoint at a time, and it shows: verbs in the URLs, dates in three formats, prices as floats, errors as "something went wrong," and a page 400 that returns the same order twice while buyers check out. Seven topics redesign the surface as resources with typed shapes, parsed once at the edge, with errors a client can act on, pagination that stays correct under writes, changes that need no version, and an honest account of what gRPC and GraphQL would have bought instead.

7 topics

The surface Marek inherited had thirty-one endpoints named after the functions that served them: POST /doHold, GET /getSeatsForEvent?id=, POST /cancelOrder. Dates came back in three formats depending on which handler wrote them. Prices were floats, and three seats at 1999 cents became euros and three of them summed to 59.970000000000006. Every error was {"error": "something went wrong"}, so the web client string-matched the ones it could and showed the rest to the buyer. And the organizer's order list used ?page=400, which on a table gaining fifty rows a minute returned the last order of one page as the first order of the next, and the support ticket said the export was missing orders that were in the database the whole time.

This chapter redesigns the surface, and every URL, shape and error it names is the one the rest of the book uses. Resources replace actions, and the UUID replaces the sequence in every order URL. Shapes get one rule each: one timestamp format, integer cents, a PATCH that can tell absent from null, enums that grow and never shrink. The boundary parses bytes into typed values exactly once, and nothing inside re-checks them. Errors take one shape, RFC 9457, and each one is decided on what it reveals. Lists page by cursor, sort only by what an index can serve, and never count what they do not need to. Changes are additive until they cannot be, and then they expand, deprecate with a date, and contract. The last topic sets REST beside gRPC and GraphQL and says plainly which caller each one is for.

None of the three wounds from Chapter 1 closes here. The chapter builds the edge that the repairs will run through: POST /orders is the endpoint Chapter 7 gives an idempotency key, GET /events/{id}/seats is the one Chapter 9 puts in Redis, and the 404 that hides another buyer's order is the decision Chapter 5 enforces. An API designed once, properly, is why the later chapters can talk about mechanics without arguing about paths.

Seven decisions, in the order a request meets them
Resourcesthe URL table
Shapesone rule per field kind
Parseonce, at the edge
Errorswhat a client may act on
Listscorrect under writes
Changewithout a v2
StyleREST, on purpose

Topics in This Chapter

Topic 12
Resources, Not Actions
Thirty-one action endpoints become fourteen lines of nouns and verbs, and every later chapter uses them. Why the sequence id stays inside, how to cancel without a DELETE, and where nesting stops.
API Design
Topic 13
Request and Response Shapes
One timestamp format, integer cents, ids as strings, and a PATCH that can tell absent from null. Why a new enum value is safe and a removed one is not, and why three booleans are a state machine written badly.
API Design
Topic 14
Validation at the Boundary
Parse, don't validate: bytes become a HoldRequest or a 422, exactly once, and nothing inside re-checks a value that carries its type. Which checks belong at the edge, which in the domain, and why sanitizing on input is wrong for every other output.
API Design
Topic 15
Errors as a Contract
RFC 9457 Problem Details on every non-2xx: a type the client switches on and a detail it must never parse. What an error reveals, why one login error covers both failures, and when 404 is chosen over 403.
API Design
Topic 16
Pagination, Filtering, Sorting
Why OFFSET 20000 lies under inserts and reads 20,050 rows to return 50. The keyset cursor on (created_at, id), the tiebreaker that makes it correct, has_more instead of a count, and a 200-row cap with exports as jobs.
Data
Topic 17
Versioning and Change
Most changes need no version, and the contract makes it so. The list that defines a breaking change, a date header translated at the routing layer, expand-then-contract, and the Deprecation and Sunset headers with a caller count behind them.
API Design
Topic 18
REST, gRPC, GraphQL
Three styles for three callers: what a CDN can cache in each, where the N+1 problem moves under GraphQL, why gRPC needs a proxy for a browser, and the five questions that choose a style without fashion.
Architecture