Errors
The HeyVisa API uses conventional HTTP status codes and returns a machine-readable error object on every failure. Use the type and code fields to branch your handling, and always log request_id when contacting support.
Error response shape
Errors are always wrapped in a top-level error object. The request_id is unique per request and lets our team trace the exact call.
json
{
"error": {
"type": "invalid_request_error",
"code": "document_unreadable",
"message": "The uploaded passport scan could not be parsed. Re-upload a clearer image.",
"param": "documents[0]",
"request_id": "req_01HXYZ8K2M",
"doc_url": "https://heyvisa.com/documentation/errors#document_unreadable"
}
}HTTP status codes
| Status | Meaning | When it happens |
|---|---|---|
| 200 OK | Success | The request completed and a body is returned. |
| 202 Accepted | Queued | A report or document was accepted for async processing. |
| 400 Bad Request | Invalid request | Malformed JSON, missing required fields, or invalid enum values. |
| 401 Unauthorized | Auth failed | Missing, malformed, or revoked API key. |
| 402 Payment Required | Billing issue | Quota exhausted with no valid payment method on file. |
| 403 Forbidden | Not permitted | Key lacks scope for the resource or workspace. |
| 404 Not Found | Missing resource | The report, document, or webhook ID does not exist. |
| 409 Conflict | State conflict | Resource was already finalised or is being mutated concurrently. |
| 422 Unprocessable | Validation failed | Request was well-formed but a document failed semantic checks. |
| 429 Too Many Requests | Rate limited | You exceeded your plan rate limit; inspect Retry-After. |
| 500 Server Error | Internal error | An unexpected error on our side; safe to retry. |
| 503 Unavailable | Temporary outage | A dependency is degraded; retry with backoff. |
Error codes
Within a status, the code field narrows the cause. The most common application-level codes are listed below.
| Code | HTTP | Meaning |
|---|---|---|
invalid_api_key | 401 | The provided key is unknown or revoked. |
insufficient_scope | 403 | Key is valid but cannot access this resource. |
quota_exceeded | 402 | Monthly report quota is used up; upgrade or add a payment method. |
document_unreadable | 422 | OCR could not parse the upload; re-upload a clearer file. |
unsupported_category | 400 | The requested visa category is not enabled for your workspace. |
rate_limited | 429 | Too many requests in the current window. |
internal_error | 500 | Unexpected failure; retry, then contact support if it persists. |
Retry guidance
- Never retry 400, 401, 403, 404, and 422 — these are deterministic and will fail again until you fix the request.
- Retry with exponential backoff on 429, 500, and 503. Start at 1s and double up to a cap of 60s, with jitter.
- For 429, honour the
Retry-Afterresponse header (in seconds) instead of your own backoff when present. - Make write requests idempotent by sending an
Idempotency-Keyheader so a retried POST does not create a duplicate report.
Always log the request_id
When you open a support ticket, include the
request_id from the error body. It lets us locate the exact request in our logs without asking for reproduction steps, and is the fastest path to a resolution.