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-Key header is included
  • Extra whitespace: Trim any leading/trailing spaces from the key

403 Forbidden

Your API key is valid but lacks permissions.
  • Check role: viewer can only read, member can write, admin can 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_after in 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 error field for machine-readable error codes
  • The message field contains human-readable descriptions
  • The details field (when present) provides additional context
  • For 5xx errors, retry with exponential backoff (1s, 2s, 4s, 8s)
Copied to clipboard