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.
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
| Status | Title | What it means | Retry? |
|---|---|---|---|
| 400 | Bad Request | A parameter is malformed: an unparseable limit, a bad bounding box, an invalid cursor. | No, fix the request |
| 401 | Unauthorized | No API key, or a key that is not valid. Returned before any database work happens. | No |
| 403 | Forbidden | The key is valid but not allowed here: an origin-restricted key used from another site, or history deeper than your plan allows. | No |
| 404 | Not Found | No record with that identifier. | No |
| 429 | Rate Limit Exceeded | Your monthly plan quota is spent. | Yes, after Retry-After |
| 429 | Too Many Requests | Per-IP throttling on an unauthenticated route. Separate from plan quota, and upgrading does not affect it. | Yes, after Retry-After |
| 500 | Internal Server Error | Our 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.