Error Handling
When something goes wrong, the Vantage API returns structured error responses. This guide covers all error types and how to handle them.
Error Response Format
All errors follow this structure:
{
"error": {
"code": "invalid_request",
"message": "Email is required",
"field": "email",
"doc_url": "https://docs.govantage.co/api-reference/errors#invalid_request"
}
}| Field | Always Present | Description |
|---|---|---|
code | Yes | Machine-readable error type |
message | Yes | Human-readable description |
field | No | Which field caused the error |
doc_url | No | Link to relevant documentation |
HTTP Status Codes
| Status | Meaning |
|---|---|
200 | Success |
201 | Created |
400 | Bad request (your fault) |
401 | Unauthorized (auth issue) |
403 | Forbidden (no permission) |
404 | Not found |
409 | Conflict (duplicate, etc.) |
422 | Validation error |
429 | Rate limited |
500 | Server error (our fault) |
Error Codes
Authentication Errors (401)
unauthorized
No API key or invalid key.
{
"error": {
"code": "unauthorized",
"message": "Invalid API key provided"
}
}Fix: Check your API key is correct and not revoked.
Permission Errors (403)
forbidden
Valid key but lacks permission.
{
"error": {
"code": "forbidden",
"message": "You do not have access to this client"
}
}Fix: Use an API key with appropriate permissions.
Not Found Errors (404)
not_found
Resource doesn’t exist.
{
"error": {
"code": "not_found",
"message": "Client not found",
"resource": "client",
"id": "cli_xxxxx"
}
}Fix: Verify the ID is correct and the resource exists.
Validation Errors (422)
invalid_request
Request body is malformed or missing required fields.
{
"error": {
"code": "invalid_request",
"message": "Email is required",
"field": "email"
}
}invalid_parameter
A specific field value is invalid.
{
"error": {
"code": "invalid_parameter",
"message": "Rate must be a positive number",
"field": "rate",
"value": -50
}
}Multiple Errors
When multiple fields fail validation:
{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"errors": [
{ "field": "email", "message": "Invalid email format" },
{ "field": "rate", "message": "Must be positive" }
]
}
}Conflict Errors (409)
duplicate
Trying to create something that already exists.
{
"error": {
"code": "duplicate",
"message": "A client with this email already exists",
"field": "email",
"existing_id": "cli_xxxxx"
}
}conflict
Operation conflicts with current state.
{
"error": {
"code": "conflict",
"message": "Invoice has already been sent"
}
}Rate Limit Errors (429)
rate_limit_exceeded
Too many requests.
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Retry after 30 seconds."
}
}Fix: Wait for Retry-After seconds and try again.
Server Errors (500)
internal_error
Something went wrong on our end.
{
"error": {
"code": "internal_error",
"message": "An unexpected error occurred",
"request_id": "req_xxxxx"
}
}Fix: Retry after a short delay. If persistent, contact support with the request_id.
Handling Errors
Basic Pattern
try {
const response = await fetch('/v1/clients', {
method: 'POST',
headers: { 'Authorization': `Bearer ${apiKey}` },
body: JSON.stringify(data)
});
if (!response.ok) {
const error = await response.json();
handleError(error);
return;
}
const result = await response.json();
// Success
} catch (networkError) {
// Network failure
}Error Handler
function handleError(error) {
switch (error.error.code) {
case 'unauthorized':
// Refresh auth or prompt login
break;
case 'not_found':
// Resource was deleted
break;
case 'rate_limit_exceeded':
// Wait and retry
break;
case 'validation_error':
// Show field errors to user
break;
default:
// Log and show generic message
}
}Retry Logic
For transient errors:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
// Rate limited - wait and retry
const retryAfter = response.headers.get('Retry-After') || 30;
await sleep(retryAfter * 1000);
continue;
}
if (response.status >= 500) {
// Server error - exponential backoff
await sleep(Math.pow(2, attempt) * 1000);
continue;
}
return response;
} catch (e) {
// Network error - retry
if (attempt === maxRetries - 1) throw e;
await sleep(Math.pow(2, attempt) * 1000);
}
}
}Debugging
Request IDs
Every response includes a request ID:
X-Request-Id: req_xxxxxInclude this when contacting support.
Verbose Mode
Add ?debug=true to see additional error details (test mode only):
{
"error": {
"code": "invalid_request",
"message": "Rate must be positive",
"debug": {
"received_value": -50,
"expected_type": "positive_integer",
"stack_trace": "..."
}
}
}Next Steps
- Rate Limits - Stay within limits
- Authentication - Fix auth errors
- API Introduction - Endpoint overview