Skip to main content

Error Handling

Ticksupply uses consistent error responses across all endpoints. This guide covers error formats, common error codes, and best practices for handling errors.

Error response format

All errors follow this structure:
{
  "error": {
    "code": "not_found",
    "message": "Subscription sub_550e8400e29b41d4a716446655440000 not found"
  }
}
FieldDescription
codeMachine-readable error code for programmatic handling
messageHuman-readable description of the error
detailsOptional additional error context (when available)
Every response also includes an X-Request-Id header (e.g., req_7f8e9d0c1b2a3456). Include this value when contacting support.

Error codes reference

CodeHTTP StatusDescription
invalid_argument400Request validation failed
unauthenticated401Missing or invalid API key
permission_denied403Valid key, but lacks permission
not_found404Resource doesn’t exist
already_exists409Resource already exists or idempotency conflict
rate_limited429Too many requests
internal500Server error (contact support if persistent)
unavailable503Service temporarily unavailable

Handling errors by type

Authentication errors (401)

{
  "error": {
    "code": "unauthenticated",
    "message": "Invalid or missing API key"
  }
}
Common causes:
  • Missing X-Api-Key header
  • Invalid or expired API key
  • Typo in the API key
Solution:
def handle_auth_error(response):
    if response.status_code == 401:
        raise ValueError(
            "Invalid API key. Check your key at "
            "https://app.ticksupply.com/api-keys"
        )

Validation errors (400)

{
  "error": {
    "code": "invalid_argument",
    "message": "Invalid datastream_id: must be a positive integer"
  }
}
Common causes:
  • Missing required parameters
  • Invalid parameter types or values
  • Malformed request body
Solution:
def handle_validation_error(response):
    if response.status_code == 400:
        error = response.json()["error"]
        raise ValueError(f"Invalid request: {error['message']}")

Not found errors (404)

{
  "error": {
    "code": "not_found",
    "message": "Subscription sub_550e8400e29b41d4a716446655440000 not found"
  }
}
Common causes:
  • Resource was deleted
  • Incorrect resource ID
  • Resource belongs to a different account
Solution:
def get_subscription_safe(subscription_id):
    response = requests.get(
        f"https://api.ticksupply.com/v1/subscriptions/{subscription_id}",
        headers={"X-Api-Key": API_KEY}
    )
    
    if response.status_code == 404:
        return None  # Resource not found
    
    response.raise_for_status()
    return response.json()

Rate limit errors (429)

{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded. Retry after 30 seconds."
  }
}
Headers included:
  • Retry-After: Seconds to wait before retrying
Solution:
def handle_rate_limit(response):
    if response.status_code == 429:
        retry_after = int(response.headers.get("Retry-After", 60))
        print(f"Rate limited. Retrying in {retry_after} seconds...")
        time.sleep(retry_after)
        return True  # Should retry
    return False

Server errors (500, 503)

{
  "error": {
    "code": "internal",
    "message": "An internal error occurred"
  }
}
Solution:
def handle_server_error(response, retry_count=0, max_retries=3):
    if response.status_code >= 500:
        if retry_count < max_retries:
            wait_time = 2 ** retry_count  # Exponential backoff
            print(f"Server error. Retrying in {wait_time} seconds...")
            time.sleep(wait_time)
            return True  # Should retry
        
        request_id = response.headers.get("X-Request-Id", "unknown")
        raise Exception(
            f"Server error after {max_retries} retries. "
            f"Request ID: {request_id} - "
            f"Contact [email protected]"
        )
    return False

Complete error handling example

import time
import requests

class TicksupplyError(Exception):
    def __init__(self, code, message, request_id=None):
        self.code = code
        self.message = message
        self.request_id = request_id
        super().__init__(f"{code}: {message} (request_id: {request_id})")

def api_request(method, path, max_retries=3, **kwargs):
    """Make an API request with comprehensive error handling."""
    url = f"https://api.ticksupply.com{path}"
    headers = kwargs.pop("headers", {})
    headers["X-Api-Key"] = API_KEY
    
    for attempt in range(max_retries + 1):
        try:
            response = requests.request(
                method, url, headers=headers, timeout=30, **kwargs
            )
            
            # Success
            if response.status_code in (200, 201, 202, 204):
                if response.status_code == 204:
                    return None
                return response.json()
            
            # Parse error response
            try:
                error = response.json().get("error", {})
            except:
                error = {"code": "unknown", "message": response.text}

            code = error.get("code", "unknown")
            message = error.get("message", "Unknown error")
            request_id = response.headers.get("X-Request-Id", "")
            
            # Rate limit - always retry
            if response.status_code == 429:
                retry_after = int(response.headers.get("Retry-After", 30))
                print(f"Rate limited. Waiting {retry_after}s...")
                time.sleep(retry_after)
                continue
            
            # Server error - retry with backoff
            if response.status_code >= 500:
                if attempt < max_retries:
                    wait_time = 2 ** attempt
                    print(f"Server error. Waiting {wait_time}s...")
                    time.sleep(wait_time)
                    continue
            
            # Client errors - don't retry
            raise TicksupplyError(code, message, request_id)
            
        except requests.exceptions.Timeout:
            if attempt < max_retries:
                print(f"Timeout. Retrying ({attempt + 1}/{max_retries})...")
                continue
            raise
        except requests.exceptions.ConnectionError:
            if attempt < max_retries:
                time.sleep(2 ** attempt)
                continue
            raise
    
    raise Exception("Max retries exceeded")

# Usage
try:
    subscription = api_request("GET", f"/v1/subscriptions/{sub_id}")
    print(f"Found subscription: {subscription['id']}")
except TicksupplyError as e:
    if e.code == "not_found":
        print("Subscription not found")
    elif e.code == "unauthenticated":
        print("Invalid API key")
    else:
        print(f"API error: {e}")

Request tracing

Every response includes an X-Request-Id header:
X-Request-Id: req_7f8e9d0c1b2a3456
Log the X-Request-Id for all requests. When contacting support, include this ID to help us investigate issues quickly.

Best practices

Don’t assume requests succeed. Check status codes and handle errors appropriately.
For retryable errors (429, 5xx), use exponential backoff to avoid overwhelming the API.
Store X-Request-Id values in your logs for debugging and support requests.
When retrying mutating operations, use the same idempotency key to avoid duplicates.
4xx errors (except 429) indicate issues with your request. Fix the request rather than retrying.

Getting help

When contacting support about errors:
  1. Include the X-Request-Id header value from the response
  2. Describe what you were trying to do
  3. Include the request method and endpoint
  4. Note the approximate time of the error
Email: [email protected]

Next steps