Reference

Errors & Quotas

HTTP status codes, quota tracking headers, and how to handle rate limiting.

HTTP status codes

StatusMeaningAction
200SuccessParse the response body.
400Bad requestCheck the request format — postcode or registration plate missing or malformed.
401UnauthorisedCheck that the X-Api-Key header is present and correct.
403ForbiddenYour plan does not include this feature (e.g. bulk on Free tier).
429Too many requestsQuota exhausted or rate limit hit. See Retry-After header.
500Server errorTransient. Retry with exponential backoff.

Quota headers

Every successful response includes these headers. Use them to track usage in real time without polling the account endpoint.

HeaderValue
X-Quota-LimitMonthly request limit, or "unlimited" for admin accounts
X-Quota-UsedRequests used this period
X-Quota-RemainingRemaining requests (min 0), or "unlimited"
X-Quota-Grace-LimitHard ceiling — limit × 1.1 on paid plans. Equals limit on the free tier (no grace buffer).
X-Quota-ResetsISO 8601 datetime when the quota period resets
X-Payment-Status"past_due" when payment has failed — action required
Retry-AfterSeconds to wait before retrying. Present on 429 responses and on 200 responses where enrichmentPending is true.

Quota exhaustion

When your monthly quota is reached, requests are blocked until the period resets. Paid plans include a 10% grace buffer above the stated quota — intended for genuine burst traffic near period end, not sustained overuse. When you first enter the grace zone, Zyfy will email you so you can upgrade before requests are blocked. The free tier has no grace buffer and hard-blocks at exactly 100 requests.

HTTP/1.1 429 Too Many Requests
Retry-After: 3600

{
  "error": "Monthly request limit reached.",
  "limit": 100,
  "used": 330,
  "resets": "2026-04-01T00:00:00Z"
}

The resets field tells you when the quota period resets. Upgrade your plan for a higher quota.

Rate limiting

In addition to the monthly quota, requests are rate-limited per minute. The limit depends on your plan:

PlanRate limit
Free10 req / min
Micro30 req / min
Starter60 req / min
Pro300 req / min
Business600 req / min
Enterprise600 req / min
HTTP/1.1 429 Too Many Requests
Retry-After: 10

Enrichment pending

This is not an HTTP error — the response is 200 OK. When a vehicle registration is not yet in our database, Zyfy returns a provisional response immediately with enrichmentPending: true and signal fields set to null. Enrichment runs in the background and typically completes within a few seconds.

What to do

  1. Check enrichmentPending in the response.
  2. If true, read the Retry-After header and wait that many seconds.
  3. Retry the same request — enrichment typically completes within seconds.
  4. Give up after 5 attempts. Each retry counts as one quota request.

Handling errors and pending enrichment

A robust client handles both 429 rate-limit responses and enrichmentPending: true in the same retry loop.

async function lookupVehicle(reg, maxAttempts = 5) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const res = await fetch(`https://zyfy.uk/v1/vehicle/${reg}`, {
      headers: {
        'X-Api-Key': process.env.ZYFY_API_KEY,
      },
    });

    // Handle rate limiting — Retry-After tells you how long to wait
    if (res.status === 429) {
      const retryAfter = parseInt(res.headers.get('Retry-After') ?? '60', 10);
      await new Promise(r => setTimeout(r, retryAfter * 1000));
      continue;
    }

    if (!res.ok) throw new Error(`Zyfy error: ${res.status}`);
    const data = await res.json();

    // Handle enrichment pending — Retry-After is also set on these 200 responses
    if (data.enrichmentPending) {
      const retryAfter = parseInt(res.headers.get('Retry-After') ?? '10', 10);
      if (attempt < maxAttempts) await new Promise(r => setTimeout(r, retryAfter * 1000));
      continue;
    }

    return data;
  }
  throw new Error('Did not complete in time — try again shortly');
}

Error response shape

All error responses return a JSON body with an error string:

{ "error": "Human-readable description." }

See also