an API is a promise, not just a URL scheme

Every design in this track eventually needs an API design step (the "A" in RESHADED) — a translation of the functional requirements into concrete interfaces a client or another service can call. A good API design isn't just picking URLs; it's making a promise about behavior that other teams will build against and can't easily change later.

resource-oriented design

For a REST API, the first design decision is identifying the nouns (resources), not the verbs. For PlateRoute's order flow, the resources are things like restaurants, orders, and drivers — and HTTP methods express the action, rather than inventing a new endpoint per action.

GET    /orders/482          # read one order
POST   /orders               # create a new order
PATCH  /orders/482           # partially update an order (e.g. cancel it)
DELETE /orders/482           # remove an order
GET    /orders?userId=910    # list a user's orders
            
A common early mistake is designing verb-shaped endpoints instead — /cancelOrder?id=482, /getUserOrders?userId=910 — which works, but throws away the predictability of the resource-oriented convention: once a client knows the shape for one resource, it can usually guess the shape for every other one.

status codes and errors are part of the contract

A response's status code is as much a part of the API contract as its body. Returning 200 OK with {"error": "payment declined"} in the body forces every caller to parse the body just to know whether the call succeeded — using the status code correctly lets clients (and load balancers, and monitoring dashboards) understand outcomes without parsing anything.
Status code rangeMeaningExample
2xxSuccess201 Created after placing an order
4xxClient error — the request itself was invalid404 Not Found for a nonexistent order, 400 Bad Request for a malformed payload
5xxServer error — the request was valid but the server failed503 Service Unavailable during an outage
Error bodies still matter for detail — a 400 response should explain which field was invalid — but the status code should never lie about the category of outcome.

versioning: designing for the change you know is coming

An API is a contract other teams' code depends on, so a breaking change (renaming a field, changing a response shape) can't just ship — it breaks every caller that hasn't updated yet. The standard fix is versioning, most simply via the URL path:

GET /v1/orders/482
GET /v2/orders/482   # new response shape, old clients keep hitting v1
            
This lets a breaking change ship as a new version while v1 keeps serving old clients unmodified, buying time for every consumer to migrate before v1 is eventually deprecated.

idempotency: the property that saves a retry from becoming a double order

Networks fail. A client that calls POST /orders and times out waiting for a response genuinely doesn't know if the order was created or not — and the safe instinct, retrying, risks placing the order twice if the first request actually succeeded server-side.
The fix is an idempotency key: the client generates a unique key per logical order attempt and sends it with the request. The server stores which keys it has already processed; if the same key arrives again, it returns the original result instead of creating a second order.

POST /orders
Idempotency-Key: 7f3a9e21-...

# retrying with the SAME key returns the original order, not a duplicate
            
This single property is what makes "just retry on failure" a safe default instead of a data-corruption risk, and it's worth designing into any endpoint that has a real-world side effect (charging a card, placing an order, sending a payment) — not just as an afterthought.

related topics

reference