Error Handling
# Error Handling
SearchJet Engine API returns structured JSON error responses across all platforms. This guide covers error formats, HTTP status codes, and handling strategies for JavaScript, Python, PHP, and other environments.
---
## Error Response Format
Every error response follows a consistent JSON structure:
```json
{
"status": "error",
"error": "validation_failed",
"message": "The q parameter is required for search requests.",
"status_code": 400,
"documentation_url": "https://docs.searchjetengine.com/errors/validation_failed"
}
```
| Field | Type | Description |
|-------|------|-------------|
| `status` | string | Always `"error"` for error responses |
| `error` | string | Machine-readable error code (snake_case) |
| `message` | string | Human-readable explanation |
| `status_code` | integer | HTTP status code |
| `documentation_url` | string | Link to relevant documentation |
---
## HTTP Status Codes
### 200 OK
Request succeeded. Used for successful GET, PUT, and search operations.
```json
{
"status": "ok",
"results": [],
"total": 0
}
```
### 201 Created
Resource successfully created. Returned by POST requests that create new resources.
```json
{
"status": "ok",
"data": {
"id": "site_abc123",
"name": "My Site",
"created_at": "2025-01-15T10:30:00Z"
}
}
```
### 400 Bad Request
The request is malformed or contains invalid parameters.
**Common causes:**
- Missing required fields in the request body
- Invalid JSON syntax
- Invalid query parameters
- Invalid date formats (use YYYY-MM-DD)
- Value out of allowed range
```json
{
"status": "error",
"error": "validation_failed",
"message": "The per_page parameter must be between 1 and 100.",
"status_code": 400
}
```
### 401 Unauthorized
Authentication failed. The API key is missing, invalid, or expired.
**Common causes:**
- Missing `Authorization` header
- Malformed Bearer token
- Expired or revoked API key
```json
{
"status": "error",
"error": "unauthorized",
"message": "Invalid API key provided.",
"status_code": 401
}
```
### 403 Forbidden
The request is authenticated but the key lacks permission for this operation.
**Common causes:**
- Public key (`sj_pub_`) used for admin operations
- Key does not have access to the requested site
- Key permissions do not include the operation
```json
{
"status": "error",
"error": "forbidden",
"message": "This key does not have permission to manage sites.",
"status_code": 403
}
```
### 404 Not Found
The requested resource does not exist.
**Common causes:**
- Incorrect `site_id` or `doc_id`
- Resource was deleted
- Typo in the endpoint URL
```json
{
"status": "error",
"error": "not_found",
"message": "Site with ID 'xyz789' was not found.",
"status_code": 404
}
```
### 429 Too Many Requests
Rate limit exceeded. Check the `Retry-After` header for wait time.
**Common causes:**
- Exceeded per-minute request limit
- Exceeded daily quota
```json
{
"status": "error",
"error": "rate_limited",
"message": "Rate limit exceeded. Try again in 45 seconds.",
"status_code": 429
}
```
Headers included:
```
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1690000045
Retry-After: 45
```
### 500 Internal Server Error
An unexpected error occurred on the server. If this persists, contact support.
```json
{
"status": "error",
"error": "internal_error",
"message": "An unexpected error occurred. Please try again later.",
"status_code": 500
}
```
---
## Handling Errors in JavaScript
```javascript
async function searchWithRetry(query, siteId) {
const maxRetries = 3;
let attempt = 0;
while (attempt < maxRetries) {
try {
const response = await fetch(
`https://app.searchjetengine.com/api/v1/search?q=${encodeURIComponent(query)}&site_id=${siteId}`,
{
headers: {
'Authorization': 'Bearer sj_prv_xxxxxxxxxxxxxxxx'
}
}
);
if (!response.ok) {
const error = await response.json();
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 5;
console.warn(`Rate limited. Retrying in ${retryAfter}s...`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
attempt++;
continue;
}
throw new Error(`${error.error}: ${error.message}`);
}
return await response.json();
} catch (err) {
if (attempt === maxRetries - 1) throw err;
attempt++;
await new Promise(r => setTimeout(r, 1000 * attempt));
}
}
}
// Usage
try {
const results = await searchWithRetry('react tutorial', 'site_abc');
console.log(results);
} catch (err) {
console.error('Search failed:', err.message);
}
```
---
## Handling Errors in Python
```python
import httpx
import time
def search_with_retry(query: str, site_id: str, max_retries: int = 3) -> dict:
url = "https://app.searchjetengine.com/api/v1/search"
headers = {"Authorization": "Bearer sj_prv_xxxxxxxxxxxxxxxx"}
for attempt in range(max_retries):
try:
response = httpx.get(
url,
params={"q": query, "site_id": site_id},
headers=headers,
timeout=10.0
)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 5))
print(f"Rate limited. Retrying in {retry_after}s...")
time.sleep(retry_after)
continue
if response.status_code >= 400:
error = response.json()
raise Exception(f"{error['error']}: {error['message']}")
return response.json()
except httpx.TimeoutException:
if attempt == max_retries - 1:
raise
time.sleep(1 * (attempt + 1))
raise Exception("Max retries exceeded")
# Usage
try:
results = search_with_retry("react tutorial", "site_abc")
print(results)
except Exception as e:
print(f"Search failed: {e}")
```
---
## Handling Errors in PHP
```php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
function searchWithRetry(string $query, string $siteId, int $maxRetries = 3): array
{
$client = new Client([
'base_uri' => 'https://app.searchjetengine.com/api/v1',
'headers' => [
'Authorization' => 'Bearer sj_prv_xxxxxxxxxxxxxxxx',
'Accept' => 'application/json'
]
]);
for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
try {
$response = $client->get('/search', [
'query' => ['q' => $query, 'site_id' => $siteId]
]);
return json_decode($response->getBody(), true);
} catch (ClientException $e) {
$response = $e->getResponse();
$statusCode = $response->getStatusCode();
$body = json_decode($response->getBody(), true);
if ($statusCode === 429) {
$retryAfter = $response->hasHeader('Retry-After')
? (int) $response->getHeaderLine('Retry-After')
: 5;
sleep($retryAfter);
continue;
}
throw new \Exception(
"{$body['error']}: {$body['message']}"
);
}
}
throw new \Exception('Max retries exceeded');
}
// Usage
try {
$results = searchWithRetry('react tutorial', 'site_abc');
print_r($results);
} catch (\Exception $e) {
echo "Search failed: " . $e->getMessage();
}
```
---
## Handling Errors in Laravel
If using the SearchJet Laravel package, errors are thrown as exceptions:
```php
use SearchJet\Facades\SearchJet;
use SearchJet\Exceptions\RateLimitedException;
use SearchJet\Exceptions\SearchJetException;
try {
$results = SearchJet::search('react tutorial', [
'site_id' => 'site_abc',
'per_page' => 10
]);
} catch (RateLimitedException $e) {
$retryAfter = $e->getRetryAfter();
Log::warning("SearchJet rate limited, retry in {$retryAfter}s");
// Retry after delay
} catch (SearchJetException $e) {
Log::error("SearchJet error: " . $e->getMessage());
}
```
---
## Rate Limit Handling
Rate limits are enforced per API key. Handle 429 responses gracefully:
1. **Check the `Retry-After` header** — it contains the number of seconds to wait
2. **Implement exponential backoff** — increase wait time on repeated failures
3. **Queue requests** — use a request queue for high-volume operations
4. **Cache results** — reduce API calls by caching frequent queries
### Exponential Backoff Strategy
| Attempt | Wait Time |
|---------|-----------|
| 1 | 1 second |
| 2 | 2 seconds |
| 3 | 4 seconds |
| 4 | 8 seconds |
Always respect the `Retry-After` header. If the header indicates 30 seconds, wait at least 30 seconds regardless of your backoff calculation.
---
## Error Codes Reference
| Error Code | HTTP Status | Description |
|------------|-------------|-------------|
| `validation_failed` | 400 | Invalid request parameters |
| `missing_parameter` | 400 | Required parameter not provided |
| `invalid_json` | 400 | Request body is not valid JSON |
| `unauthorized` | 401 | Invalid or missing API key |
| `forbidden` | 403 | Key lacks required permissions |
| `not_found` | 404 | Resource does not exist |
| `rate_limited` | 429 | Too many requests |
| `internal_error` | 500 | Server-side error |
---
## Best Practices
1. **Always handle errors** — Never assume requests will succeed
2. **Log errors** — Include the `error` code and `message` for debugging
3. **Implement retry logic** — For 429 and 500 errors with exponential backoff
4. **Validate inputs client-side** — Reduce 400 errors by validating before sending
5. **Check status before parsing** — Verify the response `status` field before accessing data
6. **Use timeouts** — Prevent hanging requests in all languages
---
## See Also
- [API Documentation](/docs/api-documentation) — Full API guide
- [API Endpoints Reference](/docs/endpoints) — All endpoints
- [API Key Setup](/docs/api-key-setup) — Authentication setup