On this page
The six steps
Step 1: Requirements before endpoints
Step 2: Find the resources
Step 3: Write the endpoints
Step 4: Shape the requests and responses
Step 5: Design the failures
Step 6: Handle change
Pagination, filtering, and sorting
Authentication and authorisation
Make writes safe to retry
A checklist before you publish
Frequently asked questions
Keep going
Related Reading
How to Design a RESTful API: Resources, Methods, and Status Codes


On This Page
The six steps
Step 1: Requirements before endpoints
Step 2: Find the resources
Step 3: Write the endpoints
Step 4: Shape the requests and responses
Step 5: Design the failures
Step 6: Handle change
Pagination, filtering, and sorting
Authentication and authorisation
Make writes safe to retry
A checklist before you publish
Frequently asked questions
Keep going
Related Reading
To design a RESTful API, work in one order every time. Find the resources, then write the endpoints. Shape the requests and responses next. Then handle errors, pagination, versioning, and authentication. Endpoints are the middle of the job, not the start.
A RESTful API is a web API where things get URLs, HTTP methods act on them, and each request carries everything the server needs. This guide takes a single example, an order service, from a blank page to a contract you could publish.
The six steps
| Step | Question it answers | Output |
|---|---|---|
| 1. Requirements | Who calls this, and to do what? | A short list of operations |
| 2. Resources | What are the nouns? | Resource names and relationships |
| 3. Endpoints | How do callers act on them? | Paths and methods |
| 4. Payloads | What goes in and out? | Request and response shapes |
| 5. Failure | What happens when things go wrong? | Status codes and error bodies |
| 6. Change | How does this survive next year? | Versioning and deprecation |
Step 1: Requirements before endpoints
Two questions decide most of the design.
Who calls it? A public API used by outside developers needs stability, clear docs, and a deprecation policy. An internal API between two services you own can change next week. Do not pay the public price for an internal API.
What are the operations, in plain words? Write them as sentences before you write any paths. For an order service: place an order, look one up, list a customer's orders, cancel one, and get its shipment status.
That list is short on purpose. Five clear operations produce a better API than twenty guessed endpoints.
Step 2: Find the resources
A resource is a noun worth addressing. Take the operations and pull out the things.
From the list above: orders, customers, and shipments. Cancel is not a resource. It is a change to an order.
Then decide how they relate. A shipment belongs to an order. An order belongs to a customer. Ownership becomes a path, and independent things get their own top level path.
A useful test: if you can imagine fetching it on its own, it is a resource. If it only makes sense inside something else, it is a sub resource or a field.
Step 3: Write the endpoints
Now the paths. Collections are plural nouns. Actions are HTTP methods, never words in the path.
POST /v1/orders Place an order
GET /v1/orders/{id} Read one order
GET /v1/customers/{id}/orders List a customer's orders
POST /v1/orders/{id}/cancel Cancel an order
GET /v1/orders/{id}/shipment Shipment status
Two decisions in there are worth defending in an interview.
Why /customers/{id}/orders and not /orders?customer_id={id}? Both work. The nested path is clearer when the list only makes sense in that context. The query parameter is better when you also filter by other things, because you can combine them. Many APIs offer both.
Why is cancel a sub path rather than a method? Cancelling is a state change with rules attached, and it is not a plain field update. When an action does not map cleanly onto create, replace, or delete, a named sub resource is the honest option. Keep those rare. If half your paths end in verbs, the resource model is wrong.
Step 4: Shape the requests and responses
The request carries only what the caller can know. The server fills in the rest.
POST /v1/orders { "customer_id": "cus_8123", "items": [ { "sku": "DG-1001", "quantity": 2 } ], "shipping_address_id": "addr_55" }
The client does not send the order id, the status, or the total. Those are the server's to decide. A field the client can set that it should not be able to set is a bug waiting to happen.
201 Created Location: /v1/orders/ord_9f2 { "id": "ord_9f2", "status": "pending", "customer_id": "cus_8123", "items": [ { "sku": "DG-1001", "quantity": 2, "unit_price": 1299 } ], "total": 2598, "currency": "USD", "created_at": "2026-08-10T09:14:22Z" }
Four habits make responses easy to live with. Return the created resource, so the client does not need a second call. Send money as an integer in the smallest unit, because floating point rounding on currency causes real bugs. Use one date format everywhere, ISO 8601 with a timezone. Keep field naming identical across every endpoint.
Step 5: Design the failures
Most of an API's quality lives in what happens when things go wrong.
Pick the status code first.
400the request was malformed.401we do not know the caller.403we do know, and the answer is no.404nothing is there.409a conflict, such as cancelling an order that already shipped.422the syntax was fine but the values were not.429too many requests.
Then return a body a program can read.
{ "error": { "code": "order_already_shipped", "message": "This order has shipped and can no longer be cancelled.", "request_id": "req_01H8XK2N" } }
The code is part of the contract, so it must stay stable once clients branch on it. The message is for humans and can be reworded. The request id is what a support engineer will ask for.
Never return 200 with an error inside. Every monitor, retry rule, and cache reads the status code.
Step 6: Handle change
An API is used far longer than it takes to build. Some callers will never update.
Version only when a change would break a working client. Removing a field, renaming one, changing its type, or making an optional input required are all breaking. Adding an optional field or a new endpoint is not.
Put the major version in the path, /v1/, so it is visible in logs and a gateway can route on it. Publish how long an old version lives, and announce the end date in the traffic with Deprecation and Sunset headers, not only by email.
Ask clients to ignore fields they do not recognise. That one habit removes most of the pressure to cut a new version at all.
Pagination, filtering, and sorting
Any endpoint that returns a list needs a limit from day one.
GET /v1/customers/{id}/orders?limit=20&cursor=eyJpZCI6OTkxfQ&status=shipped&sort=-created_at
Pagination. Offset pagination takes a page number and gets slower the deeper you go, because the database reads and discards skipped rows. Cursor pagination takes a pointer to the last item, stays fast at any depth, and does not repeat rows when new ones arrive. Use cursor for lists that grow, offset for small stable ones.
Filtering. Keep filters as query parameters named after the fields they filter. ?status=shipped is guessable. ?f=1 is not.
Sorting. One parameter, with a minus sign for descending. ?sort=-created_at is a common convention and easy to document.
Cap the page size, document the cap, and return the cap rather than an error when someone asks for more.
Authentication and authorisation
Authentication is who you are. Authorisation is what you may do. They fail differently, which is why 401 and 403 are separate codes.
Use HTTPS for everything, with no plain HTTP fallback. Use OAuth 2.0 when you act on a user's behalf, and scoped tokens so a read only client cannot write. Never accept credentials in a query string, because query strings end up in server logs and browser history.
Check authorisation on the server for every request. A client that hides a button has not protected anything.
Make writes safe to retry
A phone on a weak connection sends the same order twice. The first request worked, the response was lost, the app retried.
Accept an idempotency key on writes. The client generates a unique value and sends it as a header. The server stores the key with the result, and returns that stored result if the key arrives again.
POST /v1/orders
Idempotency-Key: 8f14e45f-ea6a-4c1b-9f2b-2a1d4f3c9b77
GET, PUT, and DELETE are already idempotent by definition. POST is the one that needs the key.
A checklist before you publish
- Every collection endpoint has a page size cap.
- Every write that creates something accepts an idempotency key.
- Every error returns a stable code, not just a sentence.
- Field naming and date formats are identical across endpoints.
- The version is in the path and the gateway routes on it.
- Rate limits are returned in headers, not only in the docs.
- The schema is the source of truth and the docs are generated from it.
- A developer who has seen two endpoints can guess the third.
Frequently asked questions
How do you design a RESTful API?
Work in order. Gather requirements, find the resources, then write endpoints as plural nouns with HTTP methods. Shape requests and responses, then design errors, pagination, versioning, and auth. Resources come before endpoints, and failure design comes before polish.
What is the difference between REST and RESTful?
REST is the architectural style. RESTful describes an API that follows it. In practice most APIs called RESTful follow the useful parts, such as resources, methods, and statelessness, and skip the strictest constraint, hypermedia.
Should REST endpoints be singular or plural?
Plural for collections, so /orders and /orders/42. The reason is consistency rather than grammar. Once a developer sees the pattern once, they can predict every other path.
How many endpoints should a REST API have?
As few as the operations require. Start from the list of things callers need to do, and resist adding an endpoint per screen. A screen specific endpoint becomes dead weight the moment the screen changes.
What makes an API RESTful rather than just HTTP?
Resources with stable URLs, HTTP methods used for their real meaning, statelessness so any server can handle any request, and consistent representations. Using HTTP to post to /doAction is HTTP, not REST.
How do I design a REST API for a system design interview?
Say the six steps out loud as you go. Interviewers grade the order and the trade offs more than the exact paths. Name idempotency, pagination, and versioning before you are asked, because those three are the follow up questions.
Keep going
Designing the contract is a skill you can practise directly. Grokking Modern API Design Interview teaches this same six step method. It applies the method to 15 complete designs, including payment, chat, booking, and file storage APIs.
For the wider round, Grokking the System Design Interview covers the caching, sharding, and replication that sit underneath a real API.
What our users say
KAUSHIK JONNADULA
Thanks for a great resource! You guys are a lifesaver. I struggled a lot in design interviews, and Grokking System Design gave me an organized process to handle a design problem. Please keep adding more questions.
Simon Barker
This is what I love about http://designgurus.io’s Grokking the coding interview course. They teach patterns rather than solutions.
ABHISHEK GUPTA
My offer from the top tech company would not have been possible without Grokking System Design. Many thanks!!
Access to 50+ courses
New content added monthly
Certificate of completion
$31.08
/month
Billed Annually
Recommended Course

Grokking the Object Oriented Design Interview
60,269+ students
4.2
Learn how to prepare for object oriented design interviews and practice common object oriented design interview questions. Master low level design interview.
View Course