API Payload Design: Names, Types, Links and Errors

Design API requests and responses: RFC 3339 dates, money in minor units, embed vs link, no leaked internals, RFC 9457 Problem Details and trace headers.

API payload and message design: common questions

How should an API represent money and dates?

Send money as an integer in minor units with an ISO 4217 currency code, like {"amount": 149950, "currency": "INR"}, and dates as RFC 3339 timestamps with an offset, like 2026-11-12T19:00:00+05:30.

Should an API return all validation errors at once?

Yes. Validate the whole request and return every field problem together, for example as an errors array in an application/problem+json body, so the user can fix everything in one try instead of one error per round trip.

Related chapters

Keep learning

Overview

"starts_at": "2026-11-12T19:00:00+05:30" Is 12/11 the 12th of November or the 11th of December? RFC 3339 (an ISO 8601 profile) with an offset leaves no doubt about the day or the time zone, and every language can parse it. "price": 1499.5 Floats cannot hold 0.1 exactly, so sums drift by a paisa. Send whole minor units (paise) and the ISO 4217 code, so ₹1,499.50 never becomes ₹1,499.4999. "status": "on_sale" Nobody knows what 3 means without the docs open. A readable enum string is self-explaining, and logs stay readable too. "id": "evt_8Kq2mZ" An integer invites maths, leaks how many events exist, and JavaScript loses precision above 2^53. An opaque string with a type prefix is safe and tells you what it points at. "vnu": "Jio World Garden" "venue_name": "Jio World Garden" Abbreviations save three bytes and cost every reader a guess. Full words in one case style (snake_case here) everywhere. Lab: fix this response for GET /events/evt_8Kq2mZ Press a field to see why it is a trap. Every field now reads like a sentence and parses the same way in every language. Lab: a search page shows 20 events, each with its venue Where does the venue go? Embed the venue in each event How many different venues are on the page? HTTP round trips bytes downloaded (rough) venue copies sent more than once One request and the page can render at once. The price: bigger payloads, and every copy of the venue is cached with the event. 21 round trips on a phone network is the classic N+1 problem: the page waits on the slowest call. Smaller payload and each venue is cached on its own, but the page needs extra calls before it can show addresses. priya@mail is missing a domain ending, like .com. Use an international number such as +91 98200 12345. HTTP/1.1 201 Created\nLocation: /bookings/bk_5Rt9 POST /events/evt_8Kq2mZ/bookings Lab: Priya’s booking form has three mistakes Server stops at the first error Server returns every error Submit booking Fix what was reported, submit again Priya fixed one field per try. Each round trip felt like the form was moving the goalposts. She saw all three problems at once, fixed them together and booked on the second try. Submit and watch how many tries it takes. Nothing is hidden now: she can fix everything in one go.

Designing the payload: names, shapes, links and errors

The URL and the method decide which door a request knocks on. The payload is what walks through it: field names, data types, how much to nest, when to link instead of copy, and what to say when something fails. Get these right and a developer can read a response without opening the docs.

Clear names and easy data types

RFC 3339 2026-11-12T19:00:00+05:30Always with an offset (or Z for UTC). ISO 4217 "on_sale", "sold_out"Readable words, not 1, 2, 3. "evt_8Kq2mZ"A string the client stores and sends back, never parses. A good field name is a full word from the business, in one case style, and says what the value is: starts_at, seats_left, is_refundable. Then pick the simplest type every language reads the same way. The response below came from a hurried first version. Fix it one field at a time:

Ready-to-use data, not raw codes

Raw: the app does the work Ready to use

Four levels of consistency

Within the API event_id in every response, the same paging everywhere Across your organisation payments and bookings APIs share money and error shapes With the domain words ticket buyers and venues already use: seat, row, hold With the world RFC 3339 dates, ISO 4217 currency, ISO 3166 country codes, HTTP status codes Each ring is wider than the last. A developer who knows one level never has to learn it again. Principle 2 covered the first ring. The outer rings matter as the company grows: if the bookings team sends amount_paise and the payments team sends price in rupees, every integration needs a translation layer. A short shared style guide, checked in review, is cheaper than fixing it after both APIs ship.

The shape of a message: parameters and element roles

Atomic parameter One plain value.GET /events/evt_8Kq2mZ Parameter list A few flat values side by side.?city=mumbai&from=2026-11-01 Parameter tree Nested objects and arrays, the usual JSON body: a booking with seats and a buyer inside. Parameter forest Several trees at the top level, such as bookings and vouchers in one bulk request. Start flat and nest only when the data really belongs together. A tree that grows ten levels deep is hard to document, validate and change. Inside a response, every element plays one of four roles: The business facts the caller came for. Facts about the data: when, which version, how many in total, where from. A stable name the caller can store and use in the next request. An address the caller can follow, so it does not have to build URLs itself. Under REST

Embed the related data, or link to it?

Embed when The client almost always needs it, it is small, and it changes about as often as the parent. Link when It is big, rarely needed, shared by many parents, or changes on its own schedule (live seat counts). Let the client choose Link by default, embed on request: ?expand=venue. GraphQL makes this the whole model. Network-efficient APIs takes expansion and response shaping further, and lets the client pick fields by design.

Don’t leak the internal model

The database row, sent as is A message designed for the caller Serialising ORM objects straight to JSON is fast to build and slow to live with. Every column rename becomes a breaking change, new columns leak automatically (that is how password hashes end up in responses, see the OWASP chapter ), and the client learns how your tables are split. Put a small mapping layer, a set of response types, between the database model and the wire. It costs a few lines per resource and lets each side change on its own. PSQLException: duplicate key value violates unique constraint \\ The same applies to errors. A stack trace tells an attacker your database, library versions and table names, and tells the user nothing. That one is really a seat conflict: map it to a 409 with a clear code, and keep the trace in your logs under the request ID (section 8).

Error feedback: informative and exhaustive

A good error answers three questions: what went wrong, where, and what to do now. For input checks it should also be exhaustive: validate the whole request and report every problem at once, not just the first. Try both servers on Priya’s form: The response above uses Problem Details, the IETF standard for HTTP error bodies (RFC 9457, which replaced RFC 7807). It has its own media type, application/problem+json, and five standard members: A short human summary of that kind. Same for every occurrence. The HTTP status code, copied in for convenience. What went wrong this time, for a human. A URI for this occurrence, handy for support tickets. Seat unavailable Seat A1 was booked by someone else. Pick another seat. the next chapter

Success feedback and context: request IDs and traces

John’s app traceparent: 00-4bf9…4736-00f0…02b7-01 API gateway adds X-Request-Id if missing Bookings service logs request ID and trace ID Payments service same trace ID, new parent span Context travels in headers, so every log line for this one booking can be found with one search. Running APIs covers the logs and dashboards that use it. Evolving an API builds on this.

In the interview

“Responses use snake_case names from the domain, RFC 3339 times with offsets, money as paise plus currency, string enums and opaque IDs. Events link to venues, with ?expand=venue for the search page. We map our tables to response types, so nothing internal leaks. Validation errors come back all at once as Problem Details with a stable code, and every response carries a request ID and trace context.” With the payload in shape, the next chapter adds the machinery around it: paging long lists, making POST safe to retry, one error envelope and versioning. Spotify’s album object: ISO 8601 times, release_date_precision, images widest first, simplified tracks inside a paging object. Payload lessons from one album Its two error shapes, compared with problem details. Spotify errors

More courses

  • Jobs, linked to lessons — Browse jobs where every requirement links to the chapter that teaches it. See how ready you are, how long the gap takes to close, and which listings look stale.
  • Match a job post to courses — Paste a job post. Each skill is matched to a track here and put in learning order, foundations first.
  • React from open source with patterns — Patterns extracted from Material UI, Radix, React Aria, React Hook Form, Redux Toolkit, TanStack, and Next.js — not slides about hooks.
  • Database in depth — How a query becomes work. Indexes, isolation, locks, and the plans that explain why a page is slow — not an ORM tour.
  • Build a real API with NestJS — One ticket-booking API, built in order: a plain Node server first, then NestJS modules, validation, a real database, and the features real clients lean on.
  • Microservices — Microservice patterns one diagram at a time: how to split a monolith, how services talk, and how data stays consistent without distributed transactions.
  • Kafka — A beginner-to-senior path through event logs, reliable delivery, production operations, and disaster recovery.
  • AI engineering for software engineers — Eight free reads, in order, for software engineers moving into AI: when to build an agent, how to build one, how to test it, and how the model works underneath.
  • Frontend system design — How a UI is sliced when it is a product, not a page: ownership of state, cache, routes, and what happens when the network lies.
  • TypeScript in depth — The type system as a design tool. Inference, narrowing, generics, and the errors that mean the model is wrong — not a catalog of utility types.
  • CSS in depth — See the box, then the page. Visual chapters from the box model through Flexbox surprises, Grid, selectors, and units, then backgrounds, shapes, and visual effects, each with a playground.
  • Accessibility — Foundations first (WCAG in plain words, semantic HTML, lists, alt text), then hands-on HTML labs, a full pass of WAI-ARIA 1.3, and a final fix-the-page challenge.
  • Docker — What an image is, why layers cache, and how containers find each other on a network.
  • Redis — Fifteen Redis patterns, one picture each: cache, session, lock, counter, rate limiter, queue, and when Redis is the wrong tool.
  • Kubernetes — Kubernetes from zero to operators: why it exists, Pods, Deployments and Services, then configuration, networking, storage, scheduling, scaling and security.