Error Handling
HTTP status codes, error response formats, and best practices for handling API errors.
The Pictograph API uses standard HTTP status codes and returns consistent error responses to help you handle issues gracefully.
HTTP Status Codes
| Code | Status | Description |
|---|---|---|
200 | OK | Request succeeded |
201 | Created | Resource created successfully |
204 | No Content | Request succeeded, no content to return (e.g., DELETE) |
400 | Bad Request | Invalid request format or parameters |
401 | Unauthorized | Missing or invalid API key |
403 | Forbidden | Valid API key but insufficient permissions |
404 | Not Found | Resource does not exist |
409 | Conflict | Resource already exists or conflict with current state |
422 | Unprocessable Entity | Request validation failed |
429 | Too Many Requests | Rate limit exceeded (see Rate Limits) |
500 | Internal Server Error | Unexpected server error |
502 | Bad Gateway | Upstream service unavailable |
503 | Service Unavailable | Service temporarily unavailable |
Error Response Format
All error responses follow a consistent JSON structure:
JSON
{
"error": "error_code",
"message": "Human-readable description of the error",
"details": {} // Optional additional context
} Example Error Responses
401 - Invalid API Key:
JSON
{
"error": "invalid_api_key",
"message": "The API key provided is invalid or has been revoked."
} 403 - Insufficient Permissions:
JSON
{
"error": "forbidden",
"message": "Your API key does not have permission to perform this action.",
"details": {
"required_role": "admin",
"current_role": "viewer"
}
} 404 - Resource Not Found:
JSON
{
"error": "not_found",
"message": "Dataset with ID 'abc123' not found."
} 422 - Validation Error:
JSON
{
"error": "validation_error",
"message": "Request validation failed",
"details": {
"annotations": [
{
"index": 0,
"field": "type",
"message": "Invalid annotation type 'box'. Must be one of: bbox, polygon, polyline, keypoint"
}
]
}
} 429 - Rate Limit Exceeded:
JSON
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded. Please retry after 120 seconds.",
"retry_after": 120
} SDK Exception Handling
The Python SDK provides a hierarchy of exceptions for easy error handling:
Python
from pictograph import Client
from pictograph.exceptions import (
PictographError, # Base exception
AuthenticationError, # 401 - Invalid/expired API key
RateLimitError, # 429 - Rate limit exceeded
NotFoundError, # 404 - Resource not found
ValidationError, # 400/422 - Invalid request
ServerError, # 5xx - Backend error
NetworkError # Connection/timeout issues
)
client = Client(api_key="pk_live_...")
try:
dataset = client.datasets.get("dataset-id")
except AuthenticationError:
print("Invalid API key - check your credentials")
except NotFoundError:
print("Dataset not found")
except RateLimitError as e:
print(f"Rate limited - wait {e.retry_after} seconds")
except ValidationError as e:
print(f"Invalid request: {e.message}")
except ServerError:
print("Server error - try again later")
except NetworkError:
print("Network error - check your connection")
except PictographError as e:
# Catch-all for any Pictograph error
print(f"Error: {e.message}") REST API Error Handling
For direct REST API usage:
Python
import requests
response = requests.get(
"https://api.pictograph.io/api/v1/developer/datasets/",
headers={"X-API-Key": "pk_live_..."}
)
if response.status_code == 200:
datasets = response.json()
elif response.status_code == 401:
print("Invalid API key")
elif response.status_code == 429:
retry_after = response.json().get("retry_after", 60)
print(f"Rate limited - wait {retry_after}s")
else:
error = response.json()
print(f"Error {response.status_code}: {error.get('message')}") Best Practices
- Check status codes first before parsing response body
- Implement retry logic with exponential backoff for 5xx errors
- Respect retry_after headers on 429 responses
- Log error details including request IDs for debugging
- Handle network errors separately from API errors
- Use the SDK which handles common error scenarios automatically
Retryable vs Non-Retryable Errors
Not all errors should be retried. Use this guide to determine the appropriate action:
| Error Type | Retryable? | Action |
|---|---|---|
400 Bad Request | No | Fix the request payload or parameters |
401 Unauthorized | No | Check/refresh API key |
403 Forbidden | No | Request higher permissions or different resource |
404 Not Found | No | Verify resource ID/path exists |
409 Conflict | Maybe | Resolve conflict, may succeed with different data |
422 Validation | No | Fix validation errors in request |
429 Rate Limited | Yes | Wait for retry_after duration |
500 Server Error | Yes | Retry with exponential backoff |
502/503 Gateway | Yes | Retry with exponential backoff |
| Network/Timeout | Yes | Retry with exponential backoff |
Troubleshooting Guide
401 Unauthorized
Common causes:
- Invalid key format: Keys must start with
pk_live_ - Revoked key: Check Settings → API Keys in the app
- Missing header: Ensure
X-API-Keyheader is included - Extra whitespace: Trim any leading/trailing spaces from the key
403 Forbidden
Your API key is valid but lacks permissions.
- Check role:
viewercan only read,membercan write,admincan delete - Check resource: You can only access resources in your organization
- Request upgrade: Ask an admin to change your API key role
404 Not Found
The resource doesn't exist or you don't have access.
- Verify ID: UUIDs are case-sensitive
- Check organization: Resource must belong to your org
- Check deletion: Resource may have been archived or deleted
- URL encoding: Special characters in names must be URL-encoded
429 Rate Limited
You've exceeded your request quota.
- Wait: Check
retry_afterin response - Batch requests: Combine multiple operations where possible
- Cache responses: Avoid redundant API calls
- Upgrade plan: Higher tiers have higher rate limits
Request/Response Logging
Enable logging to debug issues effectively:
Python
import logging
import requests
# Enable HTTP debugging
logging.basicConfig(level=logging.DEBUG)
requests_log = logging.getLogger("urllib3")
requests_log.setLevel(logging.DEBUG)
# Or use a session with hooks
def log_response(response, *args, **kwargs):
print(f"{response.request.method} {response.request.url}")
print(f"Status: {response.status_code}")
print(f"Response: {response.text[:500]}") # First 500 chars
session = requests.Session()
session.hooks['response'].append(log_response)
# All requests through this session will be logged
response = session.get(
"https://api.pictograph.io/api/v1/developer/datasets/",
headers={"X-API-Key": "pk_live_..."}
) Debugging Tips
- Check the
errorfield for machine-readable error codes - The
messagefield contains human-readable descriptions - The
detailsfield (when present) provides additional context - For 5xx errors, retry with exponential backoff (1s, 2s, 4s, 8s)