The request bounced. Your client logged the response body, parsed it, and found the error details. Within 200 milliseconds, the retry logic fired. The system recovered without human intervention.

This is the promise of well-designed API errors: machines can handle failure autonomously, and developers can sleep through incidents. But when error codes are inconsistent, undocumented, or mismatched to their transport layer, that promise collapses. Engineers spend hours debugging "unexpected token at position 0" instead of fixing the actual problem.

TickDB uses a structured error code system where 3001 means rate limit exceeded — not HTTP 429, not a generic 500, not an untyped JSON blob. This choice is deliberate, and it shapes how resilient your integration becomes under load.

The HTTP 429 Confusion

HTTP 429 "Too Many Requests" is a transport-layer status code. It tells you the server refused the request because you sent too many. It carries no structured payload by default — the body is typically plain text or HTML. Developers parsing the response have no machine-readable error code to branch on, no structured metadata to inspect, and no guarantee the response body contains anything useful.

To work around this, many APIs attach custom JSON to the 429 response:

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

{
  "code": 3001,
  "message": "Rate limit exceeded. You have sent 1001 requests in the last minute.",
  "data": null
}

This hybrid approach — HTTP status on the outside, application error code on the inside — introduces ambiguity. If your client library intercepts HTTP status codes before the body is parsed, it might handle 429 generically and never surface the application-level code field. Error handling logic becomes scattered across HTTP middleware and JSON parsing layers.

TickDB avoids this ambiguity by using HTTP 200 for application-level errors. Every response carries a structured JSON body with a predictable code field. Your client parses one schema regardless of the HTTP status. Branching logic is uniform:

if response.get("code") == 0:
    data = response.get("data")
    # proceed
elif response.get("code") == 3001:
    retry_after = int(response.headers.get("Retry-After", 5))
    time.sleep(retry_after)
    # retry
else:
    raise RuntimeError(f"TickDB error {response['code']}: {response['message']}")

This pattern is the same whether you hit an invalid symbol (2002), a bad API key (1001), or a rate limit (3001). The error domain is consistent.

What Does Error Code 3001 Actually Mean?

3001 falls within TickDB's rate limit error range (3000–3999). It indicates that the client has exceeded the allowed request volume within a time window. The server is healthy — it is simply throttling you to protect shared infrastructure.

The response includes a Retry-After header. The value represents the number of seconds to wait before the next request will be accepted. This is the only field your retry logic needs to inspect.

import os
import time
import requests

API_KEY = os.environ.get("TICKDB_API_KEY")
HEADERS = {"X-API-Key": API_KEY}

def fetch_kline(symbol, interval="1h", limit=100, retries=3):
    """
    Fetch OHLCV kline data with rate-limit-aware retry.
    
    Handles code 3001 by respecting the Retry-After header.
    Falls back to exponential backoff if no Retry-After is present.
    """
    url = "https://api.tickdb.ai/v1/market/kline"
    params = {"symbol": symbol, "interval": interval, "limit": limit}
    
    for attempt in range(retries):
        response = requests.get(
            url, headers=HEADERS, params=params, timeout=(3.05, 10)
        )
        result = response.json()
        code = result.get("code", 0)
        
        if code == 0:
            return result.get("data")
        
        if code in (1001, 1002):
            raise ValueError(
                "Invalid API key — check your TICKDB_API_KEY environment variable"
            )
        
        if code == 2002:
            raise KeyError(
                f"Symbol {symbol} not found — verify via /v1/symbols/available"
            )
        
        if code == 3001:
            retry_after = int(response.headers.get("Retry-After", 5))
            print(f"Rate limited. Waiting {retry_after}s before retry.")
            time.sleep(retry_after)
            continue
        
        raise RuntimeError(f"Unexpected TickDB error {code}: {result.get('message')}")
    
    raise RuntimeError(f"Rate limit retry exhausted after {retries} attempts")

# Usage
data = fetch_kline("AAPL.US", interval="1d", limit=500)

The Architecture: Why Unified Error Codes Matter

A unified error code system is not a cosmetic choice. It is an architectural decision that affects every consumer of the API.

Consistency Reduces Cognitive Load

When error handling follows a single pattern, developers do not need to remember different behaviors for different endpoints. The same code field, the same message field, the same data field — whether you are querying kline history, subscribing to WebSocket depth, or checking available symbols. The mental model is one schema.

Machine-Readable Branching

Structured codes enable programmatic error handling. Your code can branch on integer values with exact precision:

# Bad: string matching is fragile and slow
if "rate limit" in response.get("message", "").lower():
    handle_rate_limit()

# Good: exact code matching is deterministic
if response.get("code") == 3001:
    handle_rate_limit()

String matching breaks when the message is localized, reformatted, or paraphrased. Integer code matching is stable across all languages and all versions of the API documentation.

Telemetry and Observability

In production systems, error codes feed into monitoring pipelines. You can aggregate error distributions, alert on spike patterns, and diagnose systemic issues by observing which codes occur most frequently. With a unified system, the code field becomes a first-class dimension in your dashboards.

# Example: error telemetry hook
def handle_tickdb_error(response, attempt):
    code = response.get("code", 0)
    logger.warning(
        "TickDB request failed",
        extra={
            "error_code": code,
            "message": response.get("message"),
            "attempt": attempt,
            "retry_after": response.headers.get("Retry-After")
        }
    )

Error Code Reference: The Complete Map

TickDB organizes its error codes by category. Understanding the ranges helps you write more precise handling logic:

Range Category Examples
1001–1999 Authentication and permissions 1001: Invalid API key; 1002: Missing API key
2001–2999 Resource and data errors 2002: Symbol not found; 2003: Invalid interval parameter
3001–3999 Rate limiting and quota 3001: Rate limit exceeded
9000–9999 System errors 9001: Internal server error; 9002: Scheduled maintenance

The 3001 code sits in the rate limit range. When you see it, the response always includes a Retry-After header telling you exactly how long to wait.

Retry Logic: Beyond Basic Backoff

Simple fixed-interval retry loops are a common source of trouble. They do not account for the server's load state, and they create synchronized retry storms when many clients restart simultaneously after an outage.

A robust retry implementation combines two techniques: exponential backoff and jitter.

Exponential backoff doubles the wait time after each failure. After a 3001 response, you wait retry_after seconds. If the next request also fails, you wait retry_after * 2, then retry_after * 4, capping at some maximum like 60 seconds.

Jitter adds randomness to prevent synchronized retries across many clients:

import random
import time

def retry_with_backoff(fetch_fn, max_retries=5, base_delay=1, max_delay=60):
    """
    Retry with exponential backoff and full jitter.
    
    Full jitter: wait = random(0, min(base_delay * (2 ** attempt), max_delay))
    This prevents thundering herd where all clients retry at the same time.
    """
    for attempt in range(max_retries):
        try:
            return fetch_fn()
        except Exception as e:
            if attempt == max_retries - 1:
                raise
            
            # Full jitter: random value between 0 and the capped delay
            cap = min(base_delay * (2 ** attempt), max_delay)
            delay = random.uniform(0, cap)
            
            print(f"Attempt {attempt + 1} failed: {e}. Retrying in {delay:.2f}s.")
            time.sleep(delay)

The Retry-After header from a 3001 response gives you an authoritative value to start with. Use it as your base delay for the first retry. Only fall back to your own backoff schedule if the header is absent.

TickDB's Broader Design Philosophy: Consistency Over Convenience

The choice to use application-level error codes instead of HTTP status codes reflects a broader design principle: TickDB prioritizes developer experience at the cost of HTTP orthodoxy.

HTTP status codes are meaningful at the transport layer. 429 tells a load balancer or API gateway that the client should be throttled. But inside your application logic, a structured error code with documented semantics is far more useful.

This philosophy extends to other design decisions:

  • Authentication: REST uses X-API-Key header; WebSocket uses ?api_key= URL parameter. Each transport mechanism uses its native auth pattern, but the credential itself is the same.
  • Data granularity: depth channel supports different levels per market (L1 for US equities, L1–L10 for HK and crypto). The API surfaces these differences rather than hiding them behind a lowest-common-denominator abstraction.
  • Error messages: Every error includes a human-readable message field. These messages are stable and versioned — breaking changes to messages are treated like breaking changes to endpoints.

The goal is an API that behaves predictably even when something goes wrong.

Choosing an API Provider: Why Error Handling Should Influence Your Decision

When evaluating market data APIs, most engineers compare coverage, latency, and pricing. Error handling rarely gets equal weight — until an incident occurs.

An API that returns opaque 500 errors with no structured body forces you into manual debugging. An API that returns structured error codes with actionable metadata lets you build self-healing integrations. Over the lifetime of a production system, the difference is measured in engineering hours saved.

TickDB's error code system means that when something goes wrong, you know exactly what it is, why it happened, and how long to wait before retrying. The Retry-After header on a 3001 response is not a courtesy — it is a contract. The server is telling you: "I am healthy, but you sent too many requests. Wait this long and try again." Your code can honor that contract autonomously.

Conclusion

HTTP 429 tells you the request was denied. Error code 3001 tells you why, for how long, and what to do next. The distinction is not pedantic — it is the difference between an integration that breaks silently and one that heals itself.

A unified error code system gives your code a single schema to parse, a single branching pattern to follow, and a single set of codes to document in your internal runbooks. For production systems handling millions of requests, this consistency compounds into resilience.

When you integrate with TickDB, implement the 3001 handler properly. Respect Retry-After. Add exponential backoff with jitter. Your system will survive traffic spikes, your engineers will not be paged at 3 AM, and the market data will keep flowing.

Next Steps

If you are building an integration with TickDB, review the full error code reference in the documentation before you write production error handling. Understanding 1001, 2002, and 3001 in advance saves debugging time when incidents occur.

If you are evaluating market data APIs, test how each provider handles a 3001 equivalent under load. A robust error response with Retry-After tells you the provider has designed their system for production resilience, not just demo scenarios.

If you want to see error handling in a real strategy context, read our article on WebSocket streaming for event-driven trading — it demonstrates 3001 handling inside a live market data pipeline.

For enterprise users with specific reliability requirements, contact enterprise@tickdb.ai to discuss custom rate limit configurations and SLA guarantees.


API Error Codes at a Glance

Code Meaning Action
1001 / 1002 Invalid or missing API key Check your TICKDB_API_KEY environment variable
2002 Symbol not found Verify the symbol via /v1/symbols/available
3001 Rate limit exceeded Wait for the Retry-After duration, then retry
9001 / 9002 Internal error or maintenance Retry after a backoff interval; contact support if persistent

This article does not constitute investment advice. Markets involve risk; past performance does not guarantee future results.