Errors

HTTP status codes, body format and retry strategy - what the go~mus API returns on errors and how to respond to each cleanly.

The go~mus API responds to errors with a matching HTTP status and a JSON body in one of two shapes. This page covers the codes you actually see in practice, what the body looks like, and how to react cleanly per error class.

Status codes at a glance

CodeWhenRetry useful?
200 / 201Success-
202 AcceptedAsync process running (e.g. document virus scan), result laterYes, after a short backoff
401 UnauthorizedToken missing, expired or invalidNo, swap the token
403 ForbiddenToken valid, but the permission is missingNo, try with another user or permission
404 Not FoundResource does not exist, or invalid routeNo, check the id
410 GoneResource was there but is no longer available (e.g. expired document)No
422 Unprocessable EntityValidation or business-logic error (the most common failure class)Conditionally - only if the cause is transient (see below)
5xxServer-side errorExponential backoff, 3-5 attempts

We do not currently use 429 rate limiting actively. Even so, plan for moderate concurrency when batching.

Body format

Two variants, depending on the endpoint:

Simple error

{
  "error": "reservation not valid anymore"
}

The most common form - especially for 422 and 401. A short, human-readable message, no machine code, no locale negotiation. Log it, but map your own end-user texts for display.

Error with field list

{
  "error": "customer could not be saved",
  "errors": [
    "Email can't be blank",
    "Phone is too short (minimum is 5 characters)"
  ]
}

For validation errors on nested objects (customers, orders, requests, contacts) you also get an errors array with field validations. The strings are not machine-readable - they come straight from ActiveRecord validations. Map your own end-user texts.

Error classes with examples

401 - token problems

{ "error": "authentication error; no customer or no user present" }

Common causes:

  • Authorization header missing
  • Bearer token typo or expired
  • Token from a different instance (tokens are instance-scoped)

React: do not retry, fetch a fresh token. Useful for debugging: log the last 4 characters of the token used, so you can see which token was active.

422 - validation and business logic

{ "error": "reservation is invalid: ticket is required, quantity must be > 0" }

or

{ "error": "reservation not possible" }

Typical cases:

  • Required fields missing in POST/PUT body
  • Values out of range (date in the past, quantity > capacity)
  • State conflicts: reservation expired, slot sold out in the meantime, order already finalized
  • Configuration conflicts: ticket not bookable in current auth context, tour without matching language

Reaction depends on the sub-case:

  • Required fields missing → fix frontend validation, no retry
  • Slot sold out → re-fetch availability, ask user to pick again, no blind retry
  • Reservation expired → create a new reservation
  • Race condition (two parallel order creates on the same slot) → retry once with a small delay, then give up

404 - resource missing

{ "error": "invalid route" }

or

{ "error": "customer not found" }

Classics:

  • Numeric id that does not exist (or got archived)
  • Token in URL that never existed or is gone (reservation_token)
  • Typo in the route

React: do not retry. Show "not found" appropriately in the UI.

410 - resource is gone

Rare, but it happens with documents/:id/download and similar: the document was on the server but is now out of reach (e.g. retention period elapsed). React: do not retry, tell the user "no longer available".

5xx - server-side error

When the go~mus instance briefly does not respond (deployment, database hiccup), you see 502/503/504. React: exponential backoff, 3-5 attempts, then surface an error in the UI. With non-idempotent calls (POST order, POST reservation) be careful with retries - the server may have received and processed the call already.

Idempotency and retry

Rule of thumb:

HTTP methodSafe to retry?
GET, HEAD, OPTIONSYes
PUT, DELETEYes
POSTNo, without extra protection

What that means for POST /orders, POST /reservations, POST /requests:

  • Recognize success unambiguously on the client: 200/201 with a valid response body, otherwise do not resend.
  • On timeout, check before retrying: orders and requests have list endpoints (GET /orders, GET /requests) where the entry can be found if the server did create it. For reservations there is no lookup - in doubt let the previous token expire and create a new one.
  • Use foreign ids: when syncing customers or orders with your system, use foreign_id as an external key - the server can detect duplicates on retry, or you can in your system.

More in Best Practices.

What to log

Per failed call, at minimum:

  • HTTP status, full URL path with query params (token values masked)
  • Body excerpt of the response (error and errors array)
  • Timestamp (UTC) and correlation id if you set one
  • Last 4 characters of the bearer token (for identification without exposing the secret)

That makes debugging in support cases dramatically easier for both sides.