CSC

Errors

Error response shape, status and error codes, rate-limit headers, and retry guidance for the hosted API.

REST failures return the same envelope, so a single handler covers them:

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded."
  },
  "meta": {
    "apiVersion": "v1",
    "dataVersion": "sha256:14b24c…",
    "source": {
      "name": "CountryStateCity derivative database",
      "license": {
        "id": "ODbL-1.0"
      }
    }
  }
}

Branch on error.code rather than on message. Messages are written for humans and may be reworded; codes are part of the contract and change only with a new major version. GraphQL validation and execution failures use the standard GraphQL errors array instead of this REST envelope.

Status and error codes

Statuserror.codeCauseWhat to do
400invalid_fields, invalid_limit, invalid_offsetField selection or pagination failed validationFix the parameter named by the message
400country_required, query_too_shortA required query parameter is missing or too shortSupply the required value
400invalid_types, invalid_coordinateEntity types or WGS84 coordinates failed validationUse a supported type or valid coordinate
400invalid_json, points_required, batch_too_large, query_requiredA POST body is invalidCorrect the JSON, required value, or batch size
401invalid_api_keyMissing or unrecognised x-api-key headerCheck the header name and that the key is active
404country_not_found, not_foundThe requested country or API route has no recordVerify the ISO code or route
405method_not_allowedWrong HTTP verb for the routeData routes are GET; GraphQL is POST
413body_too_largePayload exceeded the documented capSplit the batch. See Limits & policy
429rate_limit_exceededPer-key rate limit exhaustedBack off until X-RateLimit-Reset, then retry
500internal_errorUnexpected server-side failureRetry with backoff. Report with the X-Request-Id
503authentication_not_configuredAPI-key configuration is absent, so data routes fail closedTransient on our side; retry with backoff

503 is a deployment state, not a per-request fault: the service refuses to serve data routes rather than serve them unauthenticated. Public discovery and health routes stay available.

Response headers

Normal API responses and REST errors carry the common headers below. Rate-limit headers are added only after a private request has supplied a valid API key; public routes, 401, and 503 responses therefore do not carry them.

HeaderMeaning
X-Request-IdUnique per request. Include it in any bug report
X-RateLimit-LimitRequests allowed in the current window
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetUnix timestamp in seconds when the window resets
X-Data-VersionDataset snapshot the response was served from
X-CacheWhether the response came from cache

Handling rate limits

Read the remaining budget from the headers rather than counting requests yourself — the window is enforced per key, so other processes sharing the key also consume it.

async function request(path, { retries = 3 } = {}) {
  const response = await fetch(`https://countrystatecity.tansuasici.com${path}`, {
    headers: { 'x-api-key': process.env.CSC_API_KEY },
  });

  if (response.status === 429 && retries > 0) {
    const resetAt = Number(response.headers.get('X-RateLimit-Reset')) * 1000;
    const delayMs = Number.isFinite(resetAt) ? Math.max(0, resetAt - Date.now()) : 1_000;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    return request(path, { retries: retries - 1 });
  }

  if (!response.ok) {
    const { error } = await response.json();
    throw new Error(`${error.code}: ${error.message}`);
  }

  return response.json();
}

Retry 429, 500, and 503. Do not retry 400, 401, 404, 405, or 413 — they fail the same way every time until the request itself changes.

Keep the API key server-side. A key in a browser bundle is readable by anyone, and the rate limit is shared across every caller using it.

On this page