Errors
The API uses conventional HTTP status codes and always returns errors as JSON, whether or
not you send an Accept header. A 2xx status means success; 4xx means the request needs
changing. One 3xx is used — 300 asks you to choose between equally-valid matches (see
Disambiguation).
At a glance
| Status | Meaning | When |
|---|---|---|
300 |
Multiple Choices | An identifier matches more than one time zone (a bare abbreviation code, or a convert from/to token). |
401 |
Unauthorized | The token is missing, malformed, or revoked. |
403 |
Forbidden | The account's email address isn't verified. |
404 |
Not Found | No resource matches the identifier in the URL, or the path isn't an endpoint. |
422 |
Unprocessable Entity | A query parameter failed validation. |
429 |
Too Many Requests | You exceeded the per-second rate limit or your plan's monthly quota. |
503 |
Service Unavailable | Coordinate lookup has no boundary data loaded. Retry after the Retry-After header's seconds. |
Error shape
Most errors return a single message:
{
"message": "Unauthenticated."
}
Validation errors (422) add an errors object keyed by parameter, with an array of
messages per field — show these to developers, not end users:
{
"message": "The offset must be a valid UTC offset within ±14:00 — like +01:00, -04:00, or bare minutes like 60.",
"errors": {
"offset": [
"The offset must be a valid UTC offset within ±14:00 — like +01:00, -04:00, or bare minutes like 60."
]
}
}
Disambiguation (300)
GET /v1/abbreviations/{code} — and GET /v1/convert for its from/to tokens —
return 300 Multiple Choices when an identifier matches more than one named time zone
(many share a code — AST, CST, …).
Unlike the message-only error shape it returns a candidates list:
{
"message": "The abbreviation \"AST\" matches 9 time zones. Re-request by slug, or add ?offset= or ?continent= to disambiguate.",
"candidates": [
{
"code": "AST",
"name": "Atlantic Standard Time",
"slug": "atlantic-standard-time",
"continent": "Americas",
"current": { "utc_offset": "-04:00", "is_dst": false },
"links": {
"self": "https://api.timezone.io/v1/abbreviations/atlantic-standard-time"
}
},
{
"code": "AST",
"name": "Arabian Standard Time",
"slug": "arabian-standard-time",
"continent": "Asia",
"current": { "utc_offset": "+03:00", "is_dst": false },
"links": {
"self": "https://api.timezone.io/v1/abbreviations/arabian-standard-time"
}
}
]
}
(Remaining candidates trimmed.) Recover by re-requesting with the unique slug from the
candidate you meant, or narrow with ?offset= / ?continent=.
Treat 300 as a prompt, not a failure: surface the candidates to your user the way a
search box surfaces suggestions.
Common cases
401 Unauthenticated.— Check theAuthorization: Bearer <token>header. See Authentication.403— Verify your email address, then retry. Body:{ "message": "Verify your email to use the API. Resend it at https://www.timezone.io/verify-email" }.404— The identifier doesn't exist, and the message names what was looked up:{ "message": "No timezone matches \"America/NewYork\"." }. Double-check spelling and casing (America/New_York, notAmerica/NewYork), and remember to encode+as%2B. A path that isn't an endpoint (/v1/timezoneinstead of/v1/timezones) also returns404.422— Readerrorsfor the exact parameter and rule that failed.429— Back off and retry using theRetry-Afterheader — see rate limits for a worked retry example. A longRetry-Aftermeans the monthly quota is used up: the message says how to get more, and your billing page shows this month's usage.