Every developer has encountered a 429 error at some point. The server says "too many requests," you wait a few seconds, and you retry. It works. But when you encounter a 3001 error code, the first instinct is often confusion — is this a rate limit? A server error? Did I format something wrong?

That confusion is precisely the problem TickDB set out to solve with its unified error code system. In this article, we dissect why TickDB uses error codes like 3001 instead of the familiar HTTP 429, how the system works end-to-end, and what it means for your integration's reliability.

The Problem with Mixing Protocol and Application Error Codes

HTTP status codes and application-level error codes serve fundamentally different purposes. HTTP status codes describe the result of an HTTP transaction — 200 means the server delivered something, 404 means the resource does not exist, 429 means you have exceeded the server's rate limit for that endpoint. Application error codes, on the other hand, describe the result of an operation within the API's business logic — invalid symbols, missing authentication scopes, data unavailability.

When you mix these two layers, you create ambiguity. Consider what happens when a rate limit is triggered on a WebSocket connection versus a REST endpoint:

Scenario A: HTTP 429 on REST endpoint
HTTP/1.1 429 Too Many Requests
Retry-After: 3

Scenario B: HTTP 429 on WebSocket upgrade
The connection fails silently, or you receive a 426 Upgrade Required

Neither scenario tells you why the rate limit was triggered or what to do about it. You receive a generic status code, but no structured information about which API endpoint was rate-limited, whether the limit is per-second or per-minute, or what the reset window looks like.

Generic HTTP error codes are optimized for browsers and proxies. APIs consumed by programmatic clients need something more precise.

TickDB's Error Code Architecture

TickDB separates error domains into three distinct tiers:

Tier Code range Purpose Example
Tier 1 1000–1999 Authentication and authorization 1001: Invalid API key
Tier 2 2000–2999 Resource and data errors 2002: Symbol not found
Tier 3 3000–3999 Rate limits and quota management 3001: Rate limit exceeded

This three-tier structure is intentional. When an error arrives, the code alone tells you the domain of the problem:

  • Code 1000s: Something is wrong with who you are.
  • Code 2000s: Something is wrong with what you asked for.
  • Code 3000s: Something is wrong with how often you asked.

A developer debugging an integration can triage the issue immediately by reading the first digit.

Why 3001 and Not 429?

The HTTP 429 status code exists in the HTTP specification precisely for rate limiting. So why did TickDB choose 3001 instead?

The answer lies in what the HTTP status code communicates to the client versus what the application needs to communicate internally.

HTTP 429 is a transport-layer signal. It tells the HTTP client to back off. It does not tell your application logic what the rate limit policy is, which endpoint triggered it, or how to handle the next retry intelligently.

TickDB 3001 is an application-layer signal. It tells your application that the rate limit has been exceeded, provides structured metadata about the limit, and allows your client library to implement intelligent retry logic.

Consider the actual response structure when a 3001 error occurs:

{
  "code": 3001,
  "message": "Rate limit exceeded for endpoint /v1/market/kline",
  "data": null,
  "request_id": "req_7f3a9c2d"
}

Now compare this to a raw HTTP 429 response:

HTTP/1.1 429 Too Many Requests
Retry-After: 5

The HTTP 429 response tells you to wait. The TickDB 3001 response tells you which endpoint hit its limit, allows you to correlate the error with a specific request ID, and integrates into a broader error-handling system that also covers authentication and resource errors.

When you are building a trading system that calls 10 different endpoints across REST and WebSocket connections, you need a unified error handling layer that treats all failure modes consistently. Mixing HTTP status codes with application codes would require your error handler to implement two different code paths — one for REST responses and one for WebSocket messages. TickDB's 3001 eliminates that split.

The Retry-After Standard in Practice

The Retry-After header is the standard HTTP mechanism for communicating backoff windows. TickDB implements this header for rate limit errors, but with a critical difference: the value is always expressed in seconds and is always included in the response headers, regardless of whether you are using REST or WebSocket.

import os
import time
import requests

API_KEY = os.environ.get("TICKDB_API_KEY")
BASE_URL = "https://api.tickdb.ai/v1"

def fetch_kline_with_retry(symbol, interval="1m", limit=100, max_retries=3):
    """
    Fetch OHLCV data with retry logic that respects Retry-After headers.
    
    ⚠️ This implementation uses the synchronous requests library.
    For high-frequency trading systems, replace with aiohttp for
    non-blocking I/O. The retry logic remains identical.
    """
    headers = {"X-API-Key": API_KEY}
    params = {"symbol": symbol, "interval": interval, "limit": limit}
    
    for attempt in range(max_retries):
        try:
            response = requests.get(
                f"{BASE_URL}/market/kline",
                headers=headers,
                params=params,
                timeout=(3.05, 10)  # (connect_timeout, read_timeout)
            )
            
            if response.status_code == 200:
                return response.json()
            
            # Parse TickDB application error code from response body
            body = response.json()
            error_code = body.get("code", 0)
            
            if error_code == 3001:
                # Rate limit — read Retry-After and back off
                retry_after = int(response.headers.get("Retry-After", 5))
                print(f"Rate limited. Waiting {retry_after} seconds before retry.")
                time.sleep(retry_after)
                continue
            
            # Non-retryable error — raise immediately
            handle_error(error_code, body.get("message"))
            
        except requests.exceptions.Timeout:
            print(f"Request timed out on attempt {attempt + 1}. Retrying...")
            time.sleep(2 ** attempt)  # Simple exponential backoff for timeouts
            continue
    
    raise RuntimeError(f"Failed after {max_retries} attempts")

def handle_error(code, message):
    """Route TickDB error codes to appropriate handling."""
    if code in (1001, 1002):
        raise ValueError(f"Authentication failed: {message}")
    if code == 2002:
        raise KeyError(f"Symbol not found: {message}")
    if code == 3001:
        raise RuntimeError(f"Rate limit policy issue: {message}")
    raise RuntimeError(f"Unexpected error {code}: {message}")

This pattern — reading the error code from the JSON body and the backoff window from the headers — works identically for REST and WebSocket error messages. The unified error model means your error handler does not need to branch on protocol type.

A Comparative Look: TickDB vs. Other Market Data APIs

Error handling dimension Generic REST API HTTP-only error API TickDB
Rate limit error code HTTP 429 HTTP 429 3001
Authentication error code HTTP 401 HTTP 401 1001 / 1002
Symbol not found code HTTP 404 HTTP 404 2002
Structured error body Optional Sometimes Always
Consistent code across REST and WebSocket No No Yes
Retry-After header on rate limit Sometimes Sometimes Always

The practical impact is significant. When you integrate a market data API into a production trading system, you need error handling that works reliably at 3 AM when something goes wrong. With a unified code system, your on-call engineer can read the first digit of the error code — 1, 2, or 3 — and immediately know the category of the problem. With a mixed HTTP-and-application system, they need to know whether the error came from the HTTP layer or the application layer first.

The Developer Experience Benefits

Faster debugging. Error codes that map to specific domains eliminate the need to parse error messages. When your monitoring system alerts you to a spike in errors, you can segment by code prefix without reading a single message.

Consistent retry logic. Because all rate limit errors share code 3001 regardless of which endpoint triggered them, your retry function can be written once and reused across the entire API surface.

Better observability. Structured error responses with request IDs and error codes integrate cleanly into log aggregation systems. You can build dashboards that show error rate by type — authentication failures, bad symbols, rate limits — without needing to parse free-text messages.

Cross-protocol consistency. If you use both REST endpoints and WebSocket subscriptions in your system, you encounter the same error codes in both contexts. The mental model is simpler.

Implementing a Production-Grade Error Handler

Below is a more complete error handler that demonstrates the full scope of the TickDB error code system:

import time
import random
import logging
from typing import Optional, Callable, Any

import requests

logger = logging.getLogger(__name__)

class TickDBError(Exception):
    """Base exception for all TickDB errors."""
    def __init__(self, code: int, message: str, request_id: Optional[str] = None):
        self.code = code
        self.message = message
        self.request_id = request_id
        super().__init__(f"[{code}] {message} (request_id: {request_id})")

class AuthenticationError(TickDBError):
    """Raised for codes 1001 and 1002."""
    pass

class ResourceError(TickDBError):
    """Raised for codes 2000–2999."""
    pass

class RateLimitError(TickDBError):
    """Raised for code 3001."""
    def __init__(self, code, message, request_id, retry_after: int):
        self.retry_after = retry_after
        super().__init__(code, message, request_id)

def make_request(
    method: str,
    url: str,
    headers: dict,
    params: Optional[dict] = None,
    json: Optional[dict] = None,
    max_retries: int = 3
) -> dict:
    """
    Generic request handler with exponential backoff, jitter, and
    TickDB error code routing.
    
    ⚠️ For high-frequency use cases, consider replacing requests with
    httpx or aiohttp. The error handling logic remains identical.
    """
    base_delay = 1.0
    max_delay = 30.0
    
    for attempt in range(max_retries):
        try:
            response = requests.request(
                method=method,
                url=url,
                headers=headers,
                params=params,
                json=json,
                timeout=(3.05, 10)
            )
            
            # Success — return parsed body
            if response.status_code == 200:
                return response.json()
            
            # Parse application error code from body
            body = response.json()
            code = body.get("code", 0)
            message = body.get("message", "Unknown error")
            request_id = body.get("request_id")
            
            # Route by error domain
            if code in (1001, 1002):
                raise AuthenticationError(code, message, request_id)
            
            if 2000 <= code < 3000:
                raise ResourceError(code, message, request_id)
            
            if code == 3001:
                retry_after = int(response.headers.get("Retry-After", 5))
                raise RateLimitError(code, message, request_id, retry_after)
            
            # Unknown error — do not retry
            raise TickDBError(code, message, request_id)
            
        except requests.exceptions.Timeout:
            logger.warning(f"Request timed out on attempt {attempt + 1}")
            if attempt == max_retries - 1:
                raise
            delay = min(base_delay * (2 ** attempt), max_delay)
            jitter = random.uniform(0, delay * 0.1)
            time.sleep(delay + jitter)
            continue
            
        except requests.exceptions.RequestException as e:
            logger.error(f"Network error: {e}")
            raise
            
    raise RuntimeError("Request loop exited unexpectedly")

This handler demonstrates the core principle: error handling by code domain, not by endpoint. Whether you call /v1/market/kline, /v1/market/depth, or any other endpoint, the error codes follow the same hierarchy and the same handling logic.

When 3001 Becomes a Feature, Not a Bug

Once you internalize the TickDB error code system, 3001 stops feeling like an arbitrary number and starts functioning as a control signal. A trading system that monitors its own error rate by code type can automatically adjust its request cadence — backing off when 3001 errors spike, alerting when 1001 errors indicate a key rotation issue, and logging 2002 errors to catch stale symbol lists.

The unified error code system is not just about consistency. It is about building systems that can reason about their own failures.

Summary: The Design Philosophy

TickDB uses 3001 instead of HTTP 429 because HTTP status codes are designed for the HTTP transport layer, while 3001 belongs to the application layer where your integration code lives. By keeping these layers separate and mapping all error domains to a consistent three-tier code structure, TickDB delivers three concrete benefits:

  1. Faster triage. The first digit of the code immediately identifies the error domain.
  2. Consistent retry logic. One handler handles all rate limit errors across all endpoints and protocols.
  3. Better observability. Error code segmentation integrates cleanly into monitoring and alerting pipelines.

The Retry-After header remains the backoff specification, but it is always accompanied by the structured error body that tells you exactly which endpoint triggered the limit and which request ID to reference in logs.


Next Steps

If you are integrating TickDB into a trading system, the error handling patterns demonstrated in this article should be implemented from day one. Start with the three-tier code structure as your mental model — authenticate first, validate symbols second, handle rate limits third.

If you want to see this in action, sign up at tickdb.ai to receive a free API key. The error responses documented here apply to all endpoints — REST and WebSocket — and the code examples in this article are directly runnable against a live account.

If you are evaluating enterprise data plans, the rate limit policies and error code behaviors described here apply across all tiers. Reach out to enterprise@tickdb.ai for dedicated quota management and SLA-backed response guarantees.

If you use AI coding assistants, search for and install the tickdb-market-data SKILL in your AI tool's marketplace to get pre-built error handling templates and retry logic for TickDB integrations.


This article does not constitute investment advice. Market data APIs and trading systems involve technical and financial risk; past performance of any strategy does not guarantee future results.