Core concepts

Errors

Every failure returns the same machine-readable shape, so you write one error handler rather than one per endpoint.

One shape, every endpoint

Errors follow RFC 9457 Problem Details and are served as application/problem+json. Branch on status, log detail, and show the user something of your own. Do not parse detail: it is written for a human reading a log and its wording can change.

Response
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "type": "https://emergencyapi.com/errors/bad-request",
  "title": "Bad Request",
  "status": 400,
  "detail": "limit must be <= 500 (got 9999)",
  "instance": "/api/v1/incidents"
}

type
A stable URI identifying the error class. This is the field to switch on if you want machine behaviour finer than the status code.
title
Short, stable, human-readable label for the class. Safe to display.
status
Repeats the HTTP status code, so a logged body stands alone.
detail
What went wrong with this specific request, including the offending value where that helps. Wording is not part of the contract.
instance
The path that produced it. Present on most errors, absent on a few.

The catalogue

StatusTitleWhat it meansRetry?
400Bad RequestA parameter is malformed: an unparseable limit, a bad bounding box, an invalid cursor.No, fix the request
401UnauthorizedNo API key, or a key that is not valid. Returned before any database work happens.No
403ForbiddenThe key is valid but not allowed here: an origin-restricted key used from another site, or history deeper than your plan allows.No
404Not FoundNo record with that identifier.No
429Rate Limit ExceededYour monthly plan quota is spent.Yes, after Retry-After
429Too Many RequestsPer-IP throttling on an unauthenticated route. Separate from plan quota, and upgrading does not affect it.Yes, after Retry-After
500Internal Server ErrorOur fault. If it persists, tell us what you called and when.Yes, with backoff

Two 429s that mean different things

Rate Limit Exceeded is your plan quota for the month. Upgrading fixes it, and the reset is the first of the month. Too Many Requests is per-IP throttling on unauthenticated routes such as password reset. Upgrading does nothing about it and the window is short.

Both carry Retry-After. Read it rather than retrying on a timer of your own. See rate limits.

Retry the transient, not the invalid

Retrying a 400 or a 401 will never succeed and burns quota you could be spending on data. Retry 429 and 500 only, with exponential backoff, and honour Retry-After when it is present.

A note on 500s specifically: we aggregate feeds run by small teams on varied infrastructure, and when a source goes down mid-poll the failure can surface here. It is worth retrying once before you treat it as real.