Skip to main content
Skip to Content

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"
  }
}
FieldAlways PresentDescription
codeYesMachine-readable error type
messageYesHuman-readable description
fieldNoWhich field caused the error
doc_urlNoLink to relevant documentation

HTTP Status Codes

StatusMeaning
200Success
201Created
400Bad request (your fault)
401Unauthorized (auth issue)
403Forbidden (no permission)
404Not found
409Conflict (duplicate, etc.)
422Validation error
429Rate limited
500Server 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_xxxxx

Include 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