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 the Authorization: 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, not America/NewYork), and remember to encode + as %2B. A path that isn't an endpoint (/v1/timezone instead of /v1/timezones) also returns 404.
  • 422 — Read errors for the exact parameter and rule that failed.
  • 429 — Back off and retry using the Retry-After header — see rate limits for a worked retry example. A long Retry-After means the monthly quota is used up: the message says how to get more, and your billing page shows this month's usage.