Skip to content

HTTP status codes explained

What 200/201/204/400/401/403/404/422/429/500 mean, with the bodies the API returns for each.

A REST API is mostly a set of agreed rules on top of HTTP. Status codes are the most important part of those rules because they tell your code what happened, before it even looks at the body. This page goes through the codes the GoRest API actually returns, what each one means, and how a sensible client handles them.

Success codes

200 OK

The default success response. Returned by every read endpoint and by partial updates (PATCH). The body is the resource (or array of resources) you asked for, encoded as JSON or XML based on your Accept header. There is nothing to do beyond reading the body.

HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 29

[
  { "id": 4521, "name": "Avani Iyer", "email": "avani@example.com",
    "gender": "female", "status": "active" }
]

201 Created

A successful POST to a collection. The body is the newly created resource, with the server-assigned id filled in. The Location header points at the new row. Treat 201 the same way you treat 200 (read the body), but the id is the part you usually want to keep.

204 No Content

A successful DELETE (and some empty-body PUTs). There is no response body to parse; that is what the 204 means. Do not call .json() on a 204; you will get a parse error.

Client errors (4xx)

400 Bad Request

The request was malformed before it even reached the controller. Common causes: invalid JSON in the body, an unknown query parameter set to an invalid value, or a header that the server cannot interpret. Fix the syntax of your request and try again. Retrying the same request never fixes a 400.

401 Unauthorized

The Authorization header was missing, malformed, or the token did not match an active access token. The body tells you which case ("Invalid token" for missing or wrong tokens, "Token expired" if you set an expiry on the token and it has passed):

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{ "message": "Invalid token" }

If your token used to work and stopped, check whether you regenerated it; regenerating in /my/tokens invalidates the previous value. If you copy a token from a different environment, check that you also copied it correctly: the most common cause of a 401 is a stray newline.

403 Forbidden

You authenticated successfully, but this token cannot perform this operation on this resource. On GoRest that almost always means: someone else's token created the row, and you cannot modify it. Check who owns the resource if this surprises you.

404 Not Found

The URL itself, or the id in the URL, does not point at a real resource. The API treats "you cannot see this" and "this never existed" the same way: 404 hides whether the row ever existed at all. Handle both cases in your code: a list filter that returns [] is success (empty page); a fetch by id that returns 404 means "no such row, full stop."

405 Method Not Allowed

The path is real but does not accept this verb. Easy to hit with a POST to the wrong nesting (POST /comments instead of POST /posts/:id/comments). The body lists which methods are allowed:

HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD

{ "message": "Method Not Allowed" }

415 Unsupported Media Type

Your Content-Type was not application/json or application/xml. GoRest does not parse form-encoded bodies on JSON endpoints. Set the header explicitly when you send a body.

422 Unprocessable Entity

The body parsed fine, but failed validation. The response is an array of { "field": "...", "message": "..." } entries that you can put straight into a form-error UI:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

[
  { "field": "email", "message": "can't be blank" },
  { "field": "email", "message": "is invalid" }
]

Treat 422 as a user error, not a server error. Do not retry, and do not page anyone. Show the messages next to the wrong fields.

429 Too Many Requests

Your access token used up its rate-limit budget for this minute. The X-RateLimit-Reset header tells you, in seconds, how long until you get a fresh allowance. Sleep that long and try again; there is a separate guide on this in Rate limiting and retries.

Server errors (5xx)

500 Internal Server Error

Something broke on the server. Quote the X-Request-Id header in any bug report so we can find the trace. Retrying once after a short delay is reasonable; retrying in a tight loop is not. 500s usually come together, and you will only make the underlying issue worse.

503 Service Unavailable

Down for maintenance, or briefly overloaded. The Retry-After header (or X-RateLimit-Reset if you hit a global limit) tells you when to come back. 503 is the only 5xx where retrying is clearly the correct response.

How to handle them in code

A pattern that works in any language: branch on three buckets, namely success (2xx), client error (4xx, where 401/403/404 are usually separate user-facing cases), and server/transport (5xx and network errors). The middle bucket gets shown in the UI; the third gets retried with back-off.

const res = await fetch(url, opts);
if (res.status >= 200 && res.status < 300) return res.json();
if (res.status === 401) return signIn();
if (res.status === 404) return null;
if (res.status === 422) throw new ValidationError(await res.json());
if (res.status === 429 || res.status >= 500) return retryWithBackoff();
throw new Error(`Unexpected ${res.status}`);

Test your error paths

The API supports a ?force_status=N query parameter on every endpoint that returns the named status code. Use it to check your error handling without waiting for a real failure: hit /users?force_status=429 and watch your 429 path, hit /users?force_status=500 and watch your retry path. The X-Simulated-Status header confirms the response was forced.

Related guides

Keep going