7 lessons

Request & Response Design

Required vs optional vs null, response models that are not database rows, over- and under-fetching, batch endpoints, size limits, and file uploads that bypass the API for the bytes.

RequirementConsumersResource ModelStyleContractValidationAuthorizationErrorsIdempotencyPaginationVersioningObservabilityEvolutionTrade-offs

Every lesson below names the consumers, the design question and the guarantee before recommending anything. Recommendations come with what they cost, when not to use them, and how they evolve.

Request Contracts: Required, Optional, Null and Absent

A request schema is a set of promises about what the server will accept and what each field means. Required vs optional vs nullable, "not sent" vs "sent as null", enums, defaults and the unknown-field policy decide whether the contract can grow — or whether every addition breaks someone.

Q · For every field a client can send, what does the contract say about absence, null, defaults and values it has never seen — and does the server behave the same way in every operation?
Response Contracts Are Not Database Rows

Serializing the ORM model is the fastest way to ship an endpoint and the most expensive way to own one. A response model is a purpose-built shape — stable ids, explicit types, computed fields, nothing accidental — that lets the table change without the contract noticing.

Q · What does this response promise consumers — and how much of that promise is really just the current shape of a table that nobody intended to freeze?
Over-Fetching and Under-Fetching

GET /users/42 returns 80 fields when the screen needs 3; the dashboard makes 8 calls to render once. Both are granularity mismatches between one generic contract and many specific consumers — and the fixes (sparse fieldsets, expansion, GraphQL, a BFF) each move the cost somewhere else.

Q · Whose screen is this contract shaped for — and when it is shaped for nobody in particular, who pays: the network, the server, or the client that makes eight round trips?
Batch APIs and Partial Failure

A client that needs 500 resources can make 500 requests or one. The batch endpoint saves round trips and rate-limit budget — and forces the contract to answer questions a single request never asked: what if item 217 fails, is anything rolled back, and how many requests did that just cost?

Q · When one request carries many operations, what does the contract promise about each of them — success, failure, order, atomicity — and what does the caller do with a response that is half of each?
Large Requests and Documented Limits

Every API has limits on body size, array length, string length, query complexity and file size. The only question is whether the contract states them — with a status code and the number — or whether a load balancer, a JSON parser or the OOM killer states them for you.

Q · How big can a request be before this API refuses it — in bytes, items, nesting depth and query cost — and does the client learn the limit from the docs or from a stack trace?
File Upload APIs: Authorize, Upload Directly, Confirm

The API that handles JSON should not be the pipe for a 3GB video. Create an upload resource, hand the client a signed URL to object storage, confirm completion, then process asynchronously — a four-step contract that keeps the API small, the bytes off your workers, and retries safe.

Q · Where do the bytes go, who authorizes them going there, how does the API learn the upload finished — and what does the client see while a 3GB file is being processed?
One Vocabulary: Naming and Consistency

A consumer who has learned one endpoint should be able to predict every other. Casing, id formats, timestamps, money, envelopes, error shapes and header names are decided once, written in a style guide and enforced by a linter — because consistency is the cheapest documentation an API will ever have.

Q · If a consumer learns one operation of this API, how much of the rest can they guess correctly — and what enforces that the guess keeps being right as ten teams add endpoints?