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 |
{
"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
Never retry 4xx errors (except 429). Client errors like 400, 401, 403, 404, and 422 will not resolve on retry. Fix the request first.
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 |
Add random jitter (0-500ms) to each delay to prevent thundering herd when multiple clients retry simultaneously.
Implementation Example
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;
}
}