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"
  }
}
When the server has structured context to share, the optional details field is included with free-form JSON:
{
  "error": {
    "code": "invalid_argument",
    "message": "Schema has too many columns",
    "details": {
      "max_columns": 100,
      "provided": 142
    }
  }
}
FieldDescription
codeMachine-readable error code for programmatic handling
messageHuman-readable description of the error
detailsOptional object with additional, error-specific context. Omitted when absent — do not rely on its presence.
Every response also includes an X-Request-Id header (e.g., req_7f8e9d0c1b2a3456). Include this value when contacting support.
Match on code rather than message. Message text is for humans and may change; code is the stable contract.

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
request_timeout408Request did not complete within the 10-second deadline
rate_limited429Too many requests
payment_required402Billing-related denial (plan limit, trial cap, suspended account, or grace period restriction)
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()

Request timeout errors (408)

{
  "error": {
    "code": "request_timeout",
    "message": "Request exceeded 10s deadline"
  }
}
Every endpoint has a hard 10-second deadline. If your request doesn’t finish within that window, you receive a 408 with the standard error envelope. This is rare under normal conditions — typical responses are tens of milliseconds — and usually indicates transient backend pressure. Solution: Retry with exponential backoff. If the same endpoint times out repeatedly, contact support with the X-Request-Id.
def handle_timeout(response, attempt, max_retries=3):
    if response.status_code == 408 and attempt < max_retries:
        wait_time = 2 ** attempt  # Exponential backoff
        print(f"Request timed out. Retrying in {wait_time} seconds...")
        time.sleep(wait_time)
        return True  # Should retry
    return False
Heavy operations like creating an export return 202 Accepted immediately and finish asynchronously, so they never hit the 10-second deadline. Poll the resulting job for completion instead.

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

Billing errors (402)

{
  "error": {
    "code": "payment_required",
    "message": "You have reached the export limit for your trial period. Your full plan limits will be available when your trial ends."
  }
}
Common causes:
  • Trial account has reached its stream or export size limit
  • Account is suspended for non-payment
  • Account is in a billing grace period with restricted actions
  • Plan does not include the requested action
Returned by: POST /v1/subscriptions, POST /v1/subscriptions/{id}/resume, and POST /v1/exports. Solution: Retrying does not help — the underlying billing state must change. Upgrade your plan, update your payment method, or wait for your trial to convert in billing settings.
def handle_billing_error(response):
    if response.status_code == 402:
        error = response.json()["error"]
        raise BillingError(
            f"{error['message']} "
            "Update your plan at https://app.ticksupply.com/settings/billing"
        )

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 support@ticksupply.com"
        )
    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: support@ticksupply.com

Next steps

Rate Limiting

Understand rate limits and how to handle them

Idempotency

Safely retry requests using idempotency keys

Pagination

Navigate large result sets efficiently

API Reference

Explore the complete API documentation
Last modified on May 9, 2026