Go REST

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 essentially a set of agreed conventions on top of HTTP. Status codes are the most important part of the conventions because they tell your code, before it even looks at the body, what happened. This page is a tour of the codes the Go REST 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 yourAccept 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 successfulPOST to a collection. The body is the newly-created resource, with the server-assignedid filled in. TheLocation header points at the new row. Treat 201 the same way you treat 200 (read the body), but theid is the bit you usually want to keep.

204 No Content

A successfulDELETE (and some empty-bodyPUTs). There is no response body to parse; that is what the 204 signals. Resist the temptation to 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. 400 is never recoverable by retrying the same request.

401 Unauthorized

TheAuthorization header was missing, malformed, or the token did not match an active access token. The body identifies 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-account/access-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 Go REST that almost always means: someone else's token created the row, and you cannot modify it. Re-read the resource owner if you are surprised.

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. Code defensively: a list filter that returns is success (empty page); a fetch by id that returns 404 is "no such row, full stop."

405 Method Not Allowed

The path is real but does not accept this verb. Easy to trip over withPOST to the wrong nesting (POST /comments instead ofPOST /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

YourContent-Type was notapplication/json orapplication/xml. Go REST 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 drop 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 alarm-page. Show the messages next to the offending fields.

429 Too Many Requests

Your access token spent its rate-limit budget for this minute. TheX-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 inRate limiting and retries.

Server errors (5xx)

500 Internal Server Error

Something broke on the server. Quote theX-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 are usually correlated, and you will only make the underlying issue worse.

503 Service Unavailable

Down for maintenance, or briefly overloaded. TheRetry-After header (orX-RateLimit-Reset if you tripped a global limit) tells you when to come back. 503 is the only 5xx where retrying is the explicitly 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 distinct user-facing cases), and server/transport (5xx and network errors). The middle bucket gets surfaced 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 verify 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. TheX-Simulated-Status header confirms the response was forced.

Related guides

Keep going

Back to all guides Try it in the console