> ## Documentation Index
> Fetch the complete documentation index at: https://developer.verilock.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling

> Handle Verilock API errors gracefully with structured error responses.

## Error Response Structure

All errors return a consistent JSON object:

| Field     | Type         | Description                                           |
| --------- | ------------ | ----------------------------------------------------- |
| `error`   | string       | Machine-readable error code for programmatic handling |
| `message` | string       | Human-readable description                            |
| `details` | object\|null | Field-level validation messages or additional context |

```json Example Error theme={null}
{
  "error": "validation_error",
  "message": "The given data was invalid.",
  "details": {
    "email": ["The email field is required."],
    "steps": ["The steps field must be an array."]
  }
}
```

## Error Types

| Error Code             | HTTP Status | Retryable | Description                                    |
| ---------------------- | ----------- | --------- | ---------------------------------------------- |
| `validation_error`     | 422         | No        | Field validation failed. Fix the request body. |
| `authentication_error` | 401         | No        | Invalid or missing API key.                    |
| `not_found`            | 404         | No        | Resource does not exist or was deleted.        |
| `rate_limit_exceeded`  | 429         | Yes       | Too many requests. Wait for `Retry-After`.     |
| `conflict`             | 409         | No        | Resource state conflict.                       |
| `server_error`         | 500, 503    | Yes       | Internal error. Safe to retry with backoff.    |

## Retry Strategy

<Warning>
  **Never retry 4xx errors** (except `429`). Client errors like 400, 401, 403, 404, and 422 will not resolve on retry. Fix the request first.
</Warning>

For retryable errors (`429`, `5xx`), use exponential backoff with jitter:

| Attempt | Delay     | Strategy        |
| ------- | --------- | --------------- |
| 1st     | 1 second  | Base delay      |
| 2nd     | 2 seconds | Double previous |
| 3rd     | 4 seconds | Double previous |
| 4th     | 8 seconds | Max backoff     |

<Tip>
  Add random jitter (0-500ms) to each delay to prevent thundering herd when multiple clients retry simultaneously.
</Tip>

## Implementation Example

<CodeGroup>
  ```php PHP (Guzzle) theme={null}
  use GuzzleHttp\Client;
  use GuzzleHttp\Exception\RequestException;

  $client = new Client([
      'base_uri' => 'https://verilock.io/api/v1/',
      'headers'  => [
          'Authorization' => 'Bearer ' . env('VERILOCK_API_KEY'),
          'Accept'    => 'application/json',
      ],
  ]);

  $maxRetries = 3;

  for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
      try {
          $response = $client->post('sessions', [
              'json' => $payload,
          ]);
          $data = json_decode($response->getBody(), true);
          break; // Success

      } catch (RequestException $e) {
          $status = $e->getResponse()?->getStatusCode();
          $body   = json_decode($e->getResponse()?->getBody(), true);

          if ($status === 422) {
              Log::error('Validation failed', $body['details'] ?? []);
              break; // Do not retry
          }
          if ($status === 429) {
              $wait = $e->getResponse()->getHeaderLine('Retry-After') ?: 5;
              sleep((int) $wait);
              continue;
          }
          if ($status >= 500) {
              sleep(pow(2, $attempt) + rand(0, 500) / 1000);
              continue;
          }
          Log::error($body['error'] ?? 'unknown', $body);
          break;
      }
  }
  ```

  ```python Python theme={null}
  import requests
  import time
  import random

  def api_request(method, path, **kwargs):
      url = f"https://verilock.io/api/v1/{path}"
      headers = {
          "Authorization": f"Bearer {os.environ['VERILOCK_API_KEY']}",
          "Accept": "application/json",
      }

      for attempt in range(3):
          response = requests.request(method, url, headers=headers, **kwargs)

          if response.ok:
              return response.json()

          if response.status_code == 429:
              wait = int(response.headers.get("Retry-After", 5))
              time.sleep(wait)
              continue

          if response.status_code >= 500:
              time.sleep(2 ** attempt + random.random())
              continue

          response.raise_for_status()

      raise Exception("Max retries exceeded")
  ```
</CodeGroup>
