Designing REST APIs: Resources, Nouns, and Status Codes
Pick resource-noun URLs, keep routes shallow, and wire HTTP status codes to real outcomes so clients can automate your API with confidence.
Netbay Engineering
Netbay Engineering
On this page
The route map of an API — how you name things, how deep you nest them, and what a numeric status code means — is the contract your clients build against. Get it right and client code stays small, obvious, and boring. Get it wrong and every consumer reinvents error handling on top of yours, and the support queue pays for the ambiguity. This post covers the three decisions that define an API's shape: resources, noun-based URLs, and a consistent status code contract.
Resources are nouns; actions are flows
A resource is a thing your API manages: instances, plans, invoices, api_keys. URLs should read like a directory of those nouns. GET /v1/plans lists plans; GET /v1/plans/ubuntu-4gb fetches one. Collections use plural nouns and single items drop the plural. Pick a convention and never waver, because your docs, SDKs, and clients all inherit it. If a consumer can guess an endpoint from the noun alone and be right most of the time, you have done this well.
Actions that do not fit a create-read-update-delete verb should still become nouns. Instead of POST /v1/instances/destroy, model the state change as its own resource: POST /v1/terminations with an instance_id in the body. Turning actions into requestable resources is what makes long-running work auditable — the client creates a job, polls its status, and retries it without special-casing anything. The pattern gets more valuable as your platform grows, because every one of those steps becomes inspectable and re-runnable.
Keep the vocabulary of nouns small. Every new noun is a new concept your clients must learn, cache, and permission. Ten well-chosen resources beat forty clever ones. When in doubt, prefer the noun your customers already use in support tickets and billing emails over whatever your internal tables call it.
Keep routes shallow
Nesting beyond two levels is a smell. GET /v1/services/1/plans/2/invoices/3 couples clients to your internal relationships and makes every future move painful. Prefer flat routes with the cross-resource lookup expressed as a query filter: GET /v1/invoices?plan_id=2&service_id=1. Flat routes are easier to version, document, cache, and permission, and they keep each resource's mental model independent. A client that fetches a plan should not need to know that the plan ever belonged to a service.
The two levels you keep matter too. The first segment is usually the collection, and the second is the identifier, or a sub-collection that genuinely cannot live anywhere else. Anything that is only meaningful inside a parent resource — like the result of a computation — earns nesting. Everything else stays at the top level.
Every status code is a promise
The first digit of a response code is the summary line of your contract. 2xx means it worked, 4xx means the client can fix it, and 5xx means the server is at fault. The distinction is not decoration: clients use it to decide whether retrying is safe. A request that failed validation must not be retried blindly, while a 503 may be retried with backoff. If your API returns 200 for an error, you have just deleted the client's ability to reason about your system.
- 200/201 — success; use 201 when the request created a resource and reply with its new location.
- 204 — success with no body; right for deletes and patches that return nothing.
- 400 — malformed request; attach a message a human can read.
- 401 — missing or invalid credentials; 403 — authenticated but not allowed to do this.
- 404 — resource not found; apply it to unknown paths as well so probing cannot reveal structure.
- 409 — state conflict, such as creating something with a name that already exists.
- 422 — well-formed but invalid, such as an IP range outside the allowed block.
- 429 — rate limited; return Retry-After so clients know when to come back.
- 5xx — server fault; the client should back off and retry later rather than changing the request.
Send a stable, documented status code and a matching body for every path, including the ones you hope clients never hit. The error path is the part of an API that developers read last and trust first. A consistent, boring error contract is what separates an API people automate from an API people manually double-check by reading raw responses.
# A resource read is a plain GET against a noun
curl -s https://api.netbayhosts.in/api/v1/plans -H "X-API-Key: $KEY" -H "X-API-Secret: $SECRET"
# Create a resource with POST; -i shows the 201 and its Location header
curl -s -i -X POST https://api.example.in/v1/terminations -H "X-API-Key: $KEY" -H "Content-Type: application/json" -d '{"instance_id":"vm-1234","reason":"deprovision"}'HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{"error":{"code":"unknown_plan","message":"No plan named 'ubuntu-9gb' exists.","details":[{"field":"plan","issue":"unknown value"}]}}Takeaway
If you take one thing from this post, make it this: an API is judged at the edges, on the four hundred requests, not the two hundreds. Standardize your nouns, keep routes flat, and let the status code say what happened — in the first digit and in the body — every single time.
You can practice every pattern here against the real Netbay V1 API in under a minute: sign in at netbayhosts.in, list the public VM plans with your key and secret, and study a well-shaped contract from a service that runs it in production.
Keep reading
Follow along on a real VPS
Deploy Linux in under 60 seconds
These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.
Deploy an instance