Developer documentation
One endpoint. Bearer-token auth. Sandbox mode for free. The OpenAPI 3.1 spec lives at /openapi.json if you'd rather drive everything off that.
Every request to /api/check requires a bearer API key. Keys are created in the dashboard at /app/keys and come in two modes:
rtw_live_…— calls real gov.uk and counts toward your quota.rtw_test_…— returns deterministic sandbox fixtures (see Sandbox below). Never touches gov.uk. Not billed.
Authorization: Bearer rtw_live_AbCdEfGhIjKlMnOp…POST /api/check
Verify a share code. Returns the structured gov.uk response (outcome, name, photo, PDF, conditions, reference, expiry).
{
"share_code": "AB1CD2EF3",
"date_of_birth": "1990-01-01",
"company_name": "Acme Ltd"
}GET /health
Liveness probe. Returns 200 ok. No auth required.
Dashboard API
Routes under /api/dash/* (keys, usage, billing) use a Supabase JWT in the bearer header and are documented in the OpenAPI spec.
Every 4xx and 5xx response is JSON of the form { "error": { "code", "message" } }. The HTTP status and error code are stable parts of the contract — switch on code, not the message.
| Code | HTTP | Meaning |
|---|---|---|
| INVALID_INPUT | 400 | Request body failed validation (zod errors echo). |
| UNAUTHENTICATED | 401 | Authorization header missing or malformed. |
| INVALID_KEY | 401 | Key not found, revoked, or wrong format. |
| NOT_FOUND | 404 | gov.uk reported "Share code not found". |
| DOB_MISMATCH | 422 | gov.uk rejected the date of birth for that share code. |
| PAYMENT_REQUIRED | 402 | Subscription is past_due or unpaid. Update payment in the portal. |
| QUOTA_EXCEEDED | 402 | Free tier exceeded its included quota. Upgrade or wait for the period reset. |
| RATE_LIMITED | 429 | Per-key RPS exceeded or soft throttle at 1.5× quota. Honour Retry-After. |
| BUSY | 503 | No browser slot free. Retry after the indicated Retry-After window. |
| TIMEOUT | 504 | gov.uk did not respond in time. Safe to retry. |
| GOVUK_UNEXPECTED | 502 | gov.uk's HTML shape doesn't match the parser — file a bug. |
| INTERNAL | 500 | Unhandled error. Open an issue with the X-Request-Id header value. |
Test keys (rtw_test_…) return deterministic fixtures, dispatched by the share code in your request. They never call gov.uk and never count toward your quota.
| Share code | Response |
|---|---|
| AA1AA1AA1 | ACCEPTED — skilled-worker visa |
| BB2BB2BB2 | REJECTED |
| CC3CC3CC3 | 404 NOT_FOUND |
| DD4DD4DD4 | 422 DOB_MISMATCH |
| EE5EE5EE5 | 502 GOVUK_UNEXPECTED (simulated) |
| (any other) | ACCEPTED — indefinite-leave fixture |
The wire shape is byte-identical to live mode. Build against sandbox, swap to a live key when you're confident, no surprises.
Live keys are quota-gated and rate-limited per the tier on your subscription. Every successful response includes:
X-RateLimit-Limit— your tier's included quotaX-RateLimit-Remaining— checks left in the current periodX-RateLimit-Reset— unix timestamp when the period resetsX-Request-Id— for cross-referencing with support
| Tier | Included | Soft throttle | Per-key RPS |
|---|---|---|---|
| Free | 10 | Hard cap | 5/sec |
| Pro | 100 | 1.5× included | 20/sec |
| Scale | 500 | 1.5× included | 50/sec |
| Enterprise | Custom | None | Custom |
Hitting a rate limit returns 429 RATE_LIMITED with a Retry-After header. Read Quota & billing for the full request-flow diagram and overage maths.