API & Automation·6 min read·

API Error Handling: Consistent Error Shapes

One error shape for every failure, stable machine-readable codes, and readable messages. Design the error contract before it designs you.

NB

Netbay Cloud Team

Netbay Engineering

On this page

Most APIs are designed happy-path first: the 200 and its payload get all the thought, and errors are whatever falls out of the framework. The result is a client codebase full of if-this-and-that checks written by people reverse-engineering failures under pressure. Errors are where the professionalism of an API shows, because every client hits them eventually. This post defines a single error contract and shows both sides of it.

One shape, always

Define exactly one error object and return it for every failure, whether the status is 400, 401, 404, 422, 429, or 503. The shape needs four fields:

  • code — a stable machine-readable identifier such as plan_not_found. Clients switch on this forever.
  • message — a short human-readable sentence. It should explain what happened, not expose internals.
  • details — an optional array of field-level problems for validation failures.
  • request_id — an id the user can paste into a support ticket. Log the full stack server-side, tied to that id, and never ship stack traces in responses.

Same shape for 401 as for 503. The uniformity is the point: a client writes one error type, one parser, one rendering path, and every future failure slots in for free.

Codes stay stable, messages change

The pair rule: the HTTP status class is coarse (404 vs 422), the code is fine-grained (plan_not_found vs plan_archived), and the message is free to improve. Clients must match on code, never on message text, so you can rewrite messages without breaking anyone. Never leak internals into the message or details — "connection refused on replica 3" helps an attacker more than it helps a user. That sentence belongs in the server log under request_id.

Validation failures earn their own discipline: details should carry field, issue, and frequently the rejected value, so a form can render per-field hints without fuzzy parsing.

The shape in the wild

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

{
  "error": {
    "code": "invalid_instance",
    "message": "The instance configuration is invalid.",
    "details": [
      { "field": "plan_id", "issue": "unknown plan id" },
      { "field": "region", "issue": "must be a specific datacenter id" }
    ],
    "request_id": "req_8f3a21cc"
  }
}

The client reads the same shape

The payoff is symmetric. A client that learns one shape can map it to one error type, mark retryable codes for backoff, and render validation details directly under form fields.

javascript
async function postJson(url, payload) {
  const res = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload)
  });
  const body = await res.json();
  if (!res.ok) {
    const err = new Error(body.error.message);
    err.code = body.error.code;
    err.details = body.error.details || [];
    err.status = res.status;
    throw err;
  }
  return body;
}

try {
  const vm = await postJson("/api/v1/instances", { plan: "ubuntu-4gb" });
  console.log(vm);
} catch (err) {
  if (err.code === "unknown_plan" || err.status >= 500) {
    return retryLater(err);      // 5xx is retryable, unknown_plan needs input
  }
  renderFieldErrors(err.details); // everything else maps to the form
}
every failure flows into the same shape 400 404 422 429 · 503 error { code, message, details, request_id } code = stable contract client: switch on code, retry 429/5xx once, render field errors one parser, forever

Takeaway

Errors are a feature. Pick one shape, keep codes stable while messages evolve, and never send stack traces to clients. Then every failure leaves a readable trail from user copy-paste to the exact log line.

You can test this discipline against a real API immediately: the Netbay V1 API documented at netbayhosts.in returns a uniform error body alongside its status codes, a small but telling detail when you are judging an integration.

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