Error Handling

Complete reference for API error codes, responses, and handling strategies.

Error Response Format

All API errors return a consistent JSON structure:

1{
2 "error": {
3 "code": "INVALID_REQUEST",
4 "message": "The query parameter is required",
5 "status": 400,
6 "details": {
7 "field": "query",
8 "reason": "missing_required_field"
9 },
10 "request_id": "req_1234567890",
11 "timestamp": "2024-01-15T10:30:00Z"
12 }
13}

HTTP Status Codes

2xx Success

CodeNameDescription
200OKRequest succeeded
201CreatedResource created successfully
204No ContentSuccess with no response body

4xx Client Errors

CodeNameDescription
400Bad RequestInvalid request parameters
401UnauthorizedMissing or invalid authentication
403ForbiddenInsufficient permissions
404Not FoundResource not found
409ConflictResource conflict (e.g., duplicate)
422Unprocessable EntityValid format, invalid data
429Too Many RequestsRate limit exceeded

5xx Server Errors

CodeNameDescription
500Internal Server ErrorUnexpected server error
502Bad GatewayUpstream service error
503Service UnavailableTemporary service outage
504Gateway TimeoutRequest timeout

Error Codes

Authentication Errors (AUTH_*)

AUTH_MISSING_TOKEN

Missing authentication token.

1{
2 "error": {
3 "code": "AUTH_MISSING_TOKEN",
4 "message": "Authentication token is required",
5 "status": 401
6 }
7}

Solution: Include Authorization: Bearer <token> header.

AUTH_INVALID_TOKEN

Invalid or malformed token.

1{
2 "error": {
3 "code": "AUTH_INVALID_TOKEN",
4 "message": "The provided authentication token is invalid",
5 "status": 401
6 }
7}

Solution: Get a new token from Auth0.

AUTH_EXPIRED_TOKEN

Token has expired.

1{
2 "error": {
3 "code": "AUTH_EXPIRED_TOKEN",
4 "message": "The authentication token has expired",
5 "status": 401,
6 "details": {
7 "expired_at": "2024-01-15T10:00:00Z"
8 }
9 }
10}

Solution: Refresh your Auth0 token.

AUTH_INSUFFICIENT_PERMISSIONS

Token lacks required permissions.

1{
2 "error": {
3 "code": "AUTH_INSUFFICIENT_PERMISSIONS",
4 "message": "Insufficient permissions to access this resource",
5 "status": 403,
6 "details": {
7 "required_scope": "write:foia",
8 "provided_scopes": ["read:foia"]
9 }
10 }
11}

Solution: Request token with appropriate scopes.

Validation Errors (VALIDATION_*)

VALIDATION_REQUIRED_FIELD

Required field is missing.

1{
2 "error": {
3 "code": "VALIDATION_REQUIRED_FIELD",
4 "message": "The 'query' field is required",
5 "status": 400,
6 "details": {
7 "field": "query",
8 "type": "string"
9 }
10 }
11}

VALIDATION_INVALID_FORMAT

Field format is invalid.

1{
2 "error": {
3 "code": "VALIDATION_INVALID_FORMAT",
4 "message": "Invalid email format",
5 "status": 400,
6 "details": {
7 "field": "requester_email",
8 "provided": "not-an-email",
9 "expected": "valid email address"
10 }
11 }
12}

VALIDATION_OUT_OF_RANGE

Value outside acceptable range.

1{
2 "error": {
3 "code": "VALIDATION_OUT_OF_RANGE",
4 "message": "Limit must be between 1 and 100",
5 "status": 400,
6 "details": {
7 "field": "limit",
8 "provided": 500,
9 "min": 1,
10 "max": 100
11 }
12 }
13}

Resource Errors (RESOURCE_*)

RESOURCE_NOT_FOUND

Requested resource doesn’t exist.

1{
2 "error": {
3 "code": "RESOURCE_NOT_FOUND",
4 "message": "Document not found",
5 "status": 404,
6 "details": {
7 "resource_type": "document",
8 "resource_id": "doc-123"
9 }
10 }
11}

RESOURCE_ALREADY_EXISTS

Resource with identifier already exists.

1{
2 "error": {
3 "code": "RESOURCE_ALREADY_EXISTS",
4 "message": "A request with this tracking number already exists",
5 "status": 409,
6 "details": {
7 "resource_type": "foia_request",
8 "field": "tracking_number",
9 "value": "FOI-2024-001"
10 }
11 }
12}

Rate Limit Errors (RATE_LIMIT_*)

RATE_LIMIT_EXCEEDED

Too many requests.

1{
2 "error": {
3 "code": "RATE_LIMIT_EXCEEDED",
4 "message": "Rate limit exceeded. Try again in 60 seconds",
5 "status": 429,
6 "details": {
7 "limit": 100,
8 "window": "1 hour",
9 "reset_at": "2024-01-15T11:00:00Z"
10 }
11 }
12}

Response Headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705320000
Retry-After: 60

Service Errors (SERVICE_*)

SERVICE_UNAVAILABLE

Temporary service outage.

1{
2 "error": {
3 "code": "SERVICE_UNAVAILABLE",
4 "message": "The service is temporarily unavailable",
5 "status": 503,
6 "details": {
7 "retry_after": 30,
8 "expected_recovery": "2024-01-15T11:00:00Z"
9 }
10 }
11}

SERVICE_TIMEOUT

Request timed out.

1{
2 "error": {
3 "code": "SERVICE_TIMEOUT",
4 "message": "The request timed out",
5 "status": 504,
6 "details": {
7 "timeout_seconds": 30
8 }
9 }
10}

SDK Error Handling

TypeScript

1import { TheholetruthApiError } from '@hole-foundation/sdk';
2
3try {
4 const results = await client.vectorSearch.search({
5 query: 'FOIA exemptions'
6 });
7} catch (error) {
8 if (error instanceof TheholetruthApiError) {
9 console.error('API Error:', error.status);
10 console.error('Code:', error.body.error.code);
11 console.error('Message:', error.body.error.message);
12
13 // Handle specific errors
14 switch (error.body.error.code) {
15 case 'AUTH_EXPIRED_TOKEN':
16 // Refresh token
17 break;
18 case 'RATE_LIMIT_EXCEEDED':
19 // Wait and retry
20 const retryAfter = error.body.error.details.reset_at;
21 break;
22 case 'RESOURCE_NOT_FOUND':
23 // Handle missing resource
24 break;
25 default:
26 // Generic error handling
27 break;
28 }
29 } else {
30 // Network or other error
31 console.error('Network error:', error);
32 }
33}

Python

1from hole_foundation_api.errors import TheholetruthApiError
2
3try:
4 results = client.vector_search.search(query="FOIA exemptions")
5except TheholetruthApiError as error:
6 print(f"API Error: {error.status_code}")
7 print(f"Code: {error.body['error']['code']}")
8 print(f"Message: {error.body['error']['message']}")
9
10 # Handle specific errors
11 if error.body['error']['code'] == 'AUTH_EXPIRED_TOKEN':
12 # Refresh token
13 pass
14 elif error.body['error']['code'] == 'RATE_LIMIT_EXCEEDED':
15 # Wait and retry
16 retry_after = error.body['error']['details']['reset_at']
17 elif error.body['error']['code'] == 'RESOURCE_NOT_FOUND':
18 # Handle missing resource
19 pass
20except Exception as error:
21 # Network or other error
22 print(f"Network error: {error}")

Retry Strategies

Exponential Backoff

1async function retryWithBackoff<T>(
2 fn: () => Promise<T>,
3 maxRetries = 3
4): Promise<T> {
5 for (let i = 0; i < maxRetries; i++) {
6 try {
7 return await fn();
8 } catch (error) {
9 if (error instanceof TheholetruthApiError) {
10 // Don't retry client errors (4xx)
11 if (error.status >= 400 && error.status < 500) {
12 throw error;
13 }
14
15 // Retry server errors (5xx) with backoff
16 if (i < maxRetries - 1) {
17 const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
18 await new Promise(resolve => setTimeout(resolve, delay));
19 continue;
20 }
21 }
22 throw error;
23 }
24 }
25 throw new Error('Max retries exceeded');
26}
27
28// Usage
29const results = await retryWithBackoff(() =>
30 client.vectorSearch.search({ query: 'FOIA' })
31);

Rate Limit Handling

1async function handleRateLimit<T>(fn: () => Promise<T>): Promise<T> {
2 try {
3 return await fn();
4 } catch (error) {
5 if (
6 error instanceof TheholetruthApiError &&
7 error.body.error.code === 'RATE_LIMIT_EXCEEDED'
8 ) {
9 // Wait until reset time
10 const resetAt = new Date(error.body.error.details.reset_at);
11 const waitMs = resetAt.getTime() - Date.now();
12
13 console.log(`Rate limited. Waiting ${waitMs}ms...`);
14 await new Promise(resolve => setTimeout(resolve, waitMs));
15
16 // Retry
17 return await fn();
18 }
19 throw error;
20 }
21}
22
23// Usage
24const results = await handleRateLimit(() =>
25 client.vectorSearch.search({ query: 'FOIA' })
26);

Common Error Scenarios

Invalid Authentication

1// ❌ Missing token
2const client = new HoleFoundationClient({
3 environment: 'https://api.theholefoundation.org'
4 // No token!
5});
6
7// ✅ Valid token
8const client = new HoleFoundationClient({
9 environment: 'https://api.theholefoundation.org',
10 token: 'your-jwt-token'
11});

Invalid Parameters

1// ❌ Missing required parameter
2await client.vectorSearch.search({
3 limit: 10
4 // Missing 'query'!
5});
6
7// ✅ All required parameters
8await client.vectorSearch.search({
9 query: 'FOIA exemptions',
10 limit: 10
11});

Resource Not Found

1// ❌ Non-existent resource
2try {
3 await client.vectorSearch.getDocument('invalid-id');
4} catch (error) {
5 if (
6 error instanceof TheholetruthApiError &&
7 error.body.error.code === 'RESOURCE_NOT_FOUND'
8 ) {
9 console.log('Document not found');
10 }
11}
12
13// ✅ Check existence first
14const results = await client.vectorSearch.search({
15 query: 'document title'
16});
17
18if (results.results.length > 0) {
19 const doc = await client.vectorSearch.getDocument(
20 results.results[0].id
21 );
22}

Debugging

Request ID

Every error includes a unique request_id for debugging:

1try {
2 await client.vectorSearch.search({ query: 'test' });
3} catch (error) {
4 if (error instanceof TheholetruthApiError) {
5 console.log('Request ID:', error.body.error.request_id);
6 // Report this ID to support
7 }
8}

Verbose Logging

Enable detailed logging:

1const client = new HoleFoundationClient({
2 environment: 'https://api.theholefoundation.org',
3 token: 'your-jwt-token',
4 // Add request/response logging
5 httpx_client: httpx.Client({
6 event_hooks: {
7 request: [req => console.log('Request:', req)],
8 response: [res => console.log('Response:', res)]
9 }
10 })
11});

Best Practices

1. Always Handle Errors

1// ❌ No error handling
2const results = await client.vectorSearch.search({ query: 'FOIA' });
3
4// ✅ Proper error handling
5try {
6 const results = await client.vectorSearch.search({ query: 'FOIA' });
7} catch (error) {
8 // Handle error appropriately
9 console.error('Search failed:', error);
10}

2. Validate Before Sending

1// ✅ Validate input
2function validateSearchQuery(query: string): void {
3 if (!query || query.trim().length === 0) {
4 throw new Error('Query cannot be empty');
5 }
6 if (query.length > 1000) {
7 throw new Error('Query too long (max 1000 characters)');
8 }
9}
10
11try {
12 validateSearchQuery(userInput);
13 const results = await client.vectorSearch.search({
14 query: userInput
15 });
16} catch (error) {
17 // Caught validation error before API call
18}

3. Use Appropriate Error Messages

1// ❌ Generic error
2catch (error) {
3 alert('Error!');
4}
5
6// ✅ User-friendly, specific messages
7catch (error) {
8 if (error instanceof TheholetruthApiError) {
9 switch (error.body.error.code) {
10 case 'AUTH_EXPIRED_TOKEN':
11 alert('Your session has expired. Please log in again.');
12 break;
13 case 'RATE_LIMIT_EXCEEDED':
14 alert('Too many requests. Please wait a moment and try again.');
15 break;
16 default:
17 alert(`Error: ${error.body.error.message}`);
18 }
19 }
20}

Support

If you encounter errors not documented here: