Saltar al contenido principal

Error Codes Reference

Complete catalog of Binance C2C API error codes with 8 classification categories, recovery patterns, and battle-tested solutions from 500K+ API calls.

Classification Priority

AD_OFFLINERETRY_WITH_SYNC-9000 MESSAGE CHECKRETRIABLE_WITH_BACKOFFRETRY_WITH_VOLUME_SYNCNON_RETRIABLE_BUSINESSSPECIAL_HANDLERPRICE_RANGE_LIMITUNKNOWN

Error -9000 Decoder

Error -9000 is a wrapper code. The real error is embedded in the message string:

  • Message contains "187049" / "187040" / "187031"RETRY_WITH_VOLUME_SYNC
  • Message contains "rango limitado" / "limited range"PRICE_RANGE_LIMIT
  • Otherwise → RETRIABLE_WITH_BACKOFF

Transport-Level Errors

These are HTTP status codes handled at the transport layer, before classify_binance_error() runs.

CodeNameAction
418I'm a teapot (IP Ban)Transport-level HTTP status. Handled before app-level error classification. Indicates temporary IP ban — requires significant cooldown (hours).
429Rate Limit ExceededCan appear as both HTTP status code and Binance app code. Check Retry-After header. Triggers circuit breaker recording.

Error Categories

Time Sync Errors

high
-1021-1022

Re-synchronize server time via GET /api/v3/time, wait 0.3s, then retry the request with a fresh timestamp.

Note: -1000 was previously classified here but is actually RETRIABLE_WITH_BACKOFF. Some methods enable per-method sync via BINANCE_RETRY_SYNC_ON_1000.

Show recovery code
async def handle_time_sync_error(client, original_request):
    """Recovery: sync time + retry with fresh timestamp."""
    await client.sync_time()  # GET /api/v3/time
    await asyncio.sleep(0.3)
    # Rebuild request with fresh timestamp — do NOT reuse stale one
    return await original_request()

Retriable (Backoff)

high
-1000429-9000

Exponential backoff with jitter. For 429: read Retry-After header. Circuit breaker opens after threshold failures.

-1000 has a per-method override: when BINANCE_RETRY_SYNC_ON_1000=true, the method also syncs time before retrying. Error -9000 is a wrapper — check the message string for wrapped 187xxx codes before classifying as backoff.

Show recovery code
def calculate_backoff(attempt: int, base_ms=250, cap_ms=30000) -> float:
    """Exponential backoff with full jitter."""
    max_backoff = min(cap_ms, base_ms * (2 ** attempt))
    return random.uniform(0, max_backoff) / 1000.0

# attempt=1: 0-250ms, attempt=2: 0-500ms, ..., capped at 30s
# For 429: also check resp.headers.get("Retry-After")

Volume Sync Errors

critical
187031187049187040

Sleep (configurable via ERROR_187XXX_RETRY_SLEEP_SEC, default 0.3s), re-fetch ad state, retry. Prevention: use price-only payload.

Error 187031 can also arrive wrapped inside -9000. The classifier checks the -9000 message string for '187031', '187049', or '187040' and reclassifies accordingly.

Show recovery code
async def handle_volume_sync(client, adv_no: str, price: str):
    """
    CRITICAL: Use price-only updates to PREVENT this error entirely.

    CORRECT: {"advNo": adv_no, "price": price}
    WRONG:   {"advNo": adv_no, "price": price, "surplusAmount": amount}
    """
    sleep_sec = float(os.getenv("ERROR_187XXX_RETRY_SLEEP_SEC", "0.3"))
    await asyncio.sleep(sleep_sec)
    # Re-fetch current ad state
    ad = await client.get_ad_detail(adv_no)
    # Retry with price-only payload
    return await client.update_ad(adv_no, price=price)

Business Errors (Non-Retriable)

medium
-2019-2015-3026-1013-1111

300-second cooldown block. Log the error. Do not retry — these require manual intervention.

Show recovery code
NON_RETRIABLE_PAUSE_SEC = 300  # 5 minutes

if classify_error(error) == "NON_RETRIABLE_BUSINESS":
    _non_retriable_block_until = time.time() + NON_RETRIABLE_PAUSE_SEC
    _non_retriable_last_code = error.code
    log_event("business_error", code=error.code, message=error.message)
    # Do NOT retry — requires manual fix (insufficient funds, invalid key, etc.)

Price Overlap

high
187055

Find a valid price outside the exclusion zone (current price ± 4 ticks). Max 5 attempts in 60 seconds, then 30-second cooldown.

Show recovery code
async def handle_overlap(target_price, my_prices, tick):
    """
    Binance rejects prices within ±4 ticks of your own ads.
    Example: ad at 41.37, tick=0.01 → zone [41.33, 41.41]
    """
    # Pre-avoid: check before first attempt
    if is_in_exclusion_zone(target_price, my_prices, tick):
        target_price = find_valid_price_outside_exclusion(
            target_price, my_prices, tick
        )

    # Rate limit: max 5 overlap retries in 60s
    if overlap_count > 5:
        await asyncio.sleep(30)
        return

Ad/Merchant Offline

medium
8322983230

Mark ad as paused, record reason and timestamp. Check every 15 seconds if ad came back online. Manual recovery via your application's admin interface.

Show recovery code
if error.code in (83229, 83230):
    ad_paused = True
    ad_paused_reason = (
        "Business closed" if error.code == 83229
        else "Taking a break"
    )
    # Check every 15s if merchant came back online
    # Manual recovery: clear the pause state via your admin interface

Dynamic Price Range

high

Parse the allowed range from the error message, clamp target price to the effective range (intersection of user limits and Binance range), retry.

Detected by message content, not by error code. Binance may wrap this in -9000 or other codes. Keywords: 'rango limitado', 'limited range', 'price range'.

Show recovery code
async def handle_price_range(error_message, target, user_min, user_max):
    """
    Binance imposes ~±10% of reference price. Detection:
    1. Parse from message: "rango limitado de: 34.77-42.49"
    2. Fallback: estimate from USD rate × ±9.5%
    """
    parsed = parse_binance_price_range(error_message)
    if parsed:
        low, high = parsed
    else:
        usd_rate = await get_usd_rate(fiat)
        low, high = usd_rate * 0.905, usd_rate * 1.095

    # Effective range = intersection of user and Binance ranges
    effective_low = max(user_min, low)
    effective_high = min(user_max, high)
    chosen = max(effective_low, min(target, effective_high))

    # Cache for pre-validation (TTL: ENGINE_BINANCE_RANGE_TTL_SEC)
    return chosen

Unknown Errors

low

Conservative exponential backoff. Log with full context for investigation.

Show recovery code
if classify_error(error) == "UNKNOWN":
    log_event("unknown_binance_error",
        code=error.code,
        message=error.message,
        endpoint=endpoint,
    )
    backoff = calculate_backoff(attempt)
    await asyncio.sleep(backoff)

Backoff Formula

sleep = random(0, min(cap_ms, base_ms × 2^attempt))

base_ms = 250, cap_ms = 30,000

Attempt 1:
0-500ms
Attempt 2:
0-1000ms
Attempt 3:
0-2000ms
Attempt 4:
0-4000ms
Attempt 5:
0-8000ms

HTTP 404 Handling

HTTP 404 responses are handled locally per endpoint, NOT as a global error classification. For EP-2 (getDetailByNo): 404 triggers a negative cache (TTL=60s, max 1024 entries). A sliding window tracker (threshold=2, window=600s) can mark an ad as "permanently dead" after 3600s of persistent 404 responses.

Error 187049: The Price-Only Solution

The most common error in Binance C2C API development. It occurs when surplusAmount is sent alongside price in an ad update. Binance validates surplus against a cached value that may have changed due to active trades.

Solution: Send ONLY advNo + price when updating price.

See EP-7: Update Ad for full details.

All Error Codes

CodeNameCategoryEnv Var
-1021Timestamp out of recvWindowRETRY_WITH_SYNC
-1022Invalid signatureRETRY_WITH_SYNC
-1000Unknown errorRETRIABLE_WITH_BACKOFF
-9000System error (wrapper)RETRIABLE_WITH_BACKOFF
-1102Mandatory parameter missingNON_RETRIABLE_BUSINESS
-1104Unknown parameterNON_RETRIABLE_BUSINESS
187049Ad status errorRETRY_WITH_VOLUME_SYNC
187040Ad status error (variant)RETRY_WITH_VOLUME_SYNC
187031Ad update failedRETRY_WITH_VOLUME_SYNC
187055Price overlapSPECIAL_HANDLER
-2019Margin insufficientNON_RETRIABLE_BUSINESS
-2015Invalid API key/permissionsNON_RETRIABLE_BUSINESS
-3026Order already completedNON_RETRIABLE_BUSINESS
-1013Invalid quantityNON_RETRIABLE_BUSINESS
-1111Precision over maximumNON_RETRIABLE_BUSINESS
83229Business closedAD_OFFLINE
83230Taking a breakAD_OFFLINE