Right To Work Checker

← Back to docs

Handling errors and retries

Which codes are retry-safe, which need user action, and how to honour Retry-After.

1. Switch on `code`, not the message

Every 4xx and 5xx is JSON of the form { "error": { "code", "message" } }. The HTTP status and the code string are stable contract; the message is human-readable and may change.

switch (error.code) {
  case 'BUSY':
  case 'TIMEOUT':
  case 'RATE_LIMITED':
    // retry-safe — honour Retry-After
    break;
  case 'NOT_FOUND':
  case 'DOB_MISMATCH':
  case 'INVALID_INPUT':
    // user-input problem — surface to your end user, don't retry
    break;
  case 'PAYMENT_REQUIRED':
  case 'QUOTA_EXCEEDED':
    // billing problem — page your admin, then upgrade or wait for reset
    break;
  case 'UNAUTHENTICATED':
  case 'INVALID_KEY':
    // your key is missing/wrong — investigate, don't retry
    break;
  case 'GOVUK_UNEXPECTED':
  case 'INTERNAL':
    // open an issue with X-Request-Id; safe to retry once
    break;
}

2. Honour `Retry-After`

When the server says 503 BUSY or 429 RATE_LIMITED it includes a Retry-After header (seconds). Sleep that long and retry — don't hammer.

async function checkWithBackoff(body: object, key: string, maxAttempts = 3) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const res = await fetch('https://api.rtwchecker.dev/api/check', {
      method: 'POST',
      headers: { 'content-type': 'application/json', authorization: `Bearer ${key}` },
      body: JSON.stringify(body),
    });
    if (res.ok) return res.json();
    if (res.status === 503 || res.status === 429) {
      const retry = Number(res.headers.get('retry-after') ?? '5');
      await new Promise((r) => setTimeout(r, retry * 1000));
      continue;
    }
    throw new Error(`${res.status}: ${(await res.json()).error.code}`);
  }
  throw new Error('Exhausted retries');
}

3. Log `X-Request-Id`

Every response carries an X-Request-Id header. Log it alongside your own trace ID so support can cross-reference our audit log if a request misbehaves.

4. When to surface to the end user

Only NOT_FOUND, DOB_MISMATCH, and INVALID_INPUT indicate something the applicant or your form needs to correct. All other codes are your team's problem to handle.