Skip to content
← Back to Skalablog

Published article

8 REST API design errors to fix this week

REST API design errors usually show up in two places: HTTP status codes that misdescribe what happened, and URIs that encode database operations instead of resources. Fixing the 200-instead-of-201 case, the 200-instead-of-404 case, and the PUT-only habit removes most of the ambiguity clients have to guess around.

REST API design errors that start with status codes

REST API design errors often begin with status codes, because the code is the part of the response clients read before the body. Returning HTTP 200 with an error flag inside the JSON forces every consumer to parse content to learn what happened. The HTTP status line already encodes success, creation, client error, or conflict.

The patterns below come from a Full Cycle video published on 20 December 2024, in which Wesley Willians walks through mistakes he sees in Brazilian API codebases. The fixes rest on the HTTP specification and on Roy Fielding's dissertation, Architectural Styles and the Design of Network-based Software Architectures, which defines resources broadly.

Two definitions carry most of the weight here. A safe method does not change server state when called. An idempotent method produces the same state change whether it is called once or a thousand times. REST endpoints are designed around those two properties, and status codes report the outcome.

Which status code fits create, read, update and delete

Match the status code to what happened to the resource, not to whether the database query ran. A POST that creates an order returns 201 with a Location header, not 200. A GET for an order that does not exist returns 404, even though the lookup executed successfully and the database answered cleanly.

The 404 argument is the one that produces the longest threads. Some developers return 200 with a body saying the order was not found, reasoning that the endpoint itself exists. That reasoning describes the route, not the resource. Fielding's definition treats the resource as the thing being represented, so a missing order is a missing representation.

Fielding's chapter on REST constraints is explicit that any information that can be named can be a resource: a document, an image, a temporal service. The order at /orders/1 is a resource. If it does not exist, RFC 9110, the current HTTP semantics specification, defines 404 as the correct signal.

A table makes the mapping concrete. Each row is a situation and the code the HTTP semantics specification assigns to it.

The REST status code reference table

SituationCorrect codeCommon mistake
POST creates an order201 Created200 OK with the created object
GET a non-existent order404 Not Found200 OK with error: true in the body
PUT/PATCH updates an order200 OK201 or a 200 with an updated flag
Duplicate email on create409 Conflict400 or 200 with a message string
Payload parses but fails validation422 Unprocessable Entity400 for both malformed and invalid
DELETE succeeds, nothing to return204 No Content200 with the deleted object echoed back
Malformed JSON that cannot be parsed400 Bad Request422 for both cases

Two of these rows are routinely collapsed into one. A JSON body with an unclosed bracket cannot be parsed at all, so 400 fits. A well-formed body that fails business validation is 422, because the server understood the request and rejected its content.

The 409 case covers uniqueness. If two users cannot share an email address, a create attempt with an existing email is a state conflict, not a generic bad request. RFC 9110 defines 409 for exactly that: the request conflicts with the current state of the target resource.

The 204 case is about saving bytes and ambiguity. Returning the deleted product in the body of a successful DELETE tells the client nothing the status line has not already told it. HTTP's own semantics define No Content for operations that succeed and have no representation to send back.

Why /orders/1/cancel is not a REST violation

/orders/1/cancel is a valid REST URI because cancel is a sub-resource of the order, and Fielding's dissertation permits any nameable concept to be a resource. The endpoint does not borrow a verb from the database layer; it names a state transition the client wants to trigger on a specific order.

RFC 9110 frames the HTTP methods around semantics, not around table operations. What matters for /orders/1/cancel is whether the call is safe and whether it is idempotent, and those two properties decide whether POST or PUT is the right verb.

Consider a service that lets a client pass an arbitrary order ID when creating. If the order exists, POST updates it; if it does not, POST creates it. The response differs accordingly: 200 when an existing order was modified, 201 when a new one was created. No created: true flag is needed in the body, because HTTP already has that vocabulary.

Safe, idempotent and the verb you actually want

GET and OPTIONS are safe, meaning repeated calls do not change server state. POST is neither safe nor idempotent, so calling it ten times creates ten orders. PUT and DELETE are idempotent but not safe: repeating them leaves the same final state as calling them once.

That distinction decides whether an order approval endpoint should use POST or PUT. Cancelling an order that is already cancelled changes nothing, so PUT is defensible. Approving an order that may move through multiple states, where a repeat call has a different effect, belongs on POST.

Deleting the last item in a cart exposes the same trap. DELETE /cart implies that repeated calls behave the same way, but when each call removes a different item, the effect changes with every request. Deleting by ID, or posting a selection of IDs in the body, keeps the operation honest. Note that DELETE does not carry a standard request body, so a bulk delete with criteria is usually a POST.

A minimum endpoint for a delete-all-products call is DELETE /products. An endpoint that deletes the last item in a cart is not that, because the resource is not stable between calls.

PATCH versus PUT and the REST URI conventions that hold

PATCH exists so a client can send only the fields that changed. A PUT request replaces the full representation, which means the client must send every field even when it wants to update one. Requiring PUT everywhere forces clients to read the resource first, mutate one field, and write the whole object back.

PATCH arrived after the original HTTP/1.1 methods. It is defined in RFC 5789, which allows a partial representation to be carried in the request body and applied to the target resource. A client that wants to change only a product name can use PATCH with one field and skip the rest of the representation entirely.

Pluralisation, nested URIs and which rule is actually a rule

Pluralising collection URIs such as /orders is a convention, not a rule from Fielding's dissertation. The convention is useful because it produces a uniform interface, and a uniform interface is one of the constraints REST defines. Consistency inside a service matters more than which of the two styles you pick.

Singular URIs have a place. /cart names the current user's cart, and /me names the authenticated user. Writing /users/me is acceptable, and so is /me alone. Either way, the resource being named is a relationship between the client and the server, not a table row.

Nested URIs express relationships that would otherwise need documentation. /categories/1/products is clearer than /products?categoryId=1 when the category is the parent context. GET /invoices/1/payments beats a bare GET /payments, because payments can belong to invoices, subscriptions, or refunds, and the nested form names the parent.

REST was designed for comparison across endpoints. A single endpoint that accepts many query parameters and returns different shapes based on them shifts cognitive load onto the client and hides the relationships the URI could have shown in the path.

Pagination, caching and the endpoint that does everything

Pagination parameters such as ?page=1&limit=100 need validation and a response that says what the server actually applied. If the maximum page size is 50 and the client asked for 100, the response should report the effective limit, not silently return 50 rows and let the client assume otherwise.

Separating collections also enables HTTP caching. /chapters can be cacheable for all students while /progress stays private, because progress depends on the authenticated user. Merging progress into a chapter listing makes the combined response uncacheable, so every student pays for data that only one of them needs.

Caching here means the standard HTTP mechanisms: Cache-Control, ETags, If-None-Match, and Last-Modified. These are protocol-level features defined in RFC 9110, and they only work when the response is scoped to a resource that is actually shared. A chapter list is shared; another user's progress is not.

A return shape such as { data: [...], meta: { page, limit } } gives clients a predictable envelope. JSON:API, an established specification for JSON response structures, formalises this pattern for APIs that need a stricter contract. The stable part is that the shape never changes between endpoints, so a client can parse it once.

The 2024 Full Cycle lesson, and where you can learn the same material

Wesley Willians delivered this material in a Full Cycle video published on 20 December 2024. Full Cycle is a Brazilian software school, and the school runs REST, architecture, DevOps, authentication and database tracks across its training programmes. Developers who want the same content in more depth can start with Full Cycle and compare it with alternatives such as Crazystack, which publishes the Crazystack Typescript material.

Free resources cover the same ground. Channels such as Gustavo Dev Doido walk through API patterns for developers who want a second explanation, and structured programmes such as the Bootcamp do Dev Doido package the material into project-based tracks. None of these are prerequisites; the primary sources for the protocol rules are the RFC documents themselves.

The video is part one of a two-part series. Part two covers further anti-patterns, and the two together map closely to what the RFCs and Fielding's dissertation actually require.

Frequently asked questions

  • Is HTTP 200 with an error flag in the body ever acceptable?
  • Should a missing resource return 404 even if the query ran successfully?
  • When is 422 Unprocessable Entity the right code?
  • Which verb should I use for a bulk delete with criteria?
  • Is /orders/1/cancel a REST violation?
  • Does REST require plural collection names?
  • What does 204 No Content tell a client that 200 does not?
  • Why is PATCH better than PUT for partial updates?
  • Do safe and idempotent HTTP methods change my status code choices?

Answers on the nine REST API design questions

  • Is HTTP 200 with an error flag in the body ever acceptable? No; the status code exists to carry that information. A 200 with error: true forces every client to parse the body before it can decide what happened, which is the opposite of a uniform interface defined in RFC 9110.
  • Should a missing resource return 404 even if the query ran successfully? Yes; the status code describes the resource representation, not whether the server executed a query. If /orders/1 does not exist, RFC 9110 defines 404 Not Found as the correct signal regardless of how the lookup went.
  • When is 422 Unprocessable Entity the right code? When the server received and parsed the request but rejected the content inside it. Malformed JSON that cannot be parsed at all is 400 Bad Request, because the request never became processable in the first place.
  • Which verb should I use for a bulk delete with criteria? Use POST with the IDs in the request body. RFC 9110 does not define a standard body for DELETE, and a deletion whose effect changes between calls is not idempotent, which removes DELETE from the candidate list.
  • Is /orders/1/cancel a REST violation? No; Fielding's dissertation allows any nameable concept to be a resource, and a state transition named as a sub-resource fits that definition. The verb choice depends on whether the operation is idempotent, not on whether the path ends in a verb-like word.
  • Does REST require plural collection names? No; plural collections are a widespread convention for a uniform interface, not a rule in Fielding's dissertation. Singular URIs such as /cart and /me are equally valid when the resource is tied to the authenticated client.
  • What does 204 No Content tell a client that 200 does not? It says the request succeeded and that there is no representation to send back. A successful DELETE that echoes the deleted object wastes bytes and adds nothing, because the client already knows the deletion worked.
  • Why is PATCH better than PUT for partial updates? PATCH carries only the fields that changed, while PUT replaces the full representation. A client renaming a product can send one field with PATCH instead of reading, mutating and rewriting the whole object.
  • Do safe and idempotent HTTP methods change my status code choices? They change the verb choice more than the status code. Safe methods never mutate server state, and idempotent methods produce the same state whether called once or repeatedly, which decides between POST and PUT on transition endpoints.

Turning a protocol walkthrough into a written reference

REST API design errors like the ones above are easy to describe once someone has shown you the RFC clauses and the endpoint examples side by side. A video that walks a viewer through 201, 404, 409, 422 and 204 already contains the raw material for a written reference, another developer can search, skim, and cite.

If you have knowledge like this sitting inside a YouTube recording, whether a protocol explainer, a design review, or a lesson from a project that went badly, you can turn that existing material into an article without rerecording anything. Paste the video URL into Skala Blog, let the transcript come back, and generate the written draft from it.

Source video

YouTube thumbnail: Estes erros vão prejudicar sua API REST - parte 1

Source video

Estes erros vão prejudicar sua API REST - parte 1

Create from this video

This article was created automatically from a new video.

Automate my channel

Turn your YouTube videos into articles

Built for creators with a backlog and no editor on staff. This page started as a video — Skalablog drafts from the transcript, you review every word, then publish on a first-party /p/{slug} page. Nothing goes live until you publish it.

Turn your videos into articles

Published with Skalablog