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
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.
| Code | Name | Action |
|---|---|---|
| 418 | I'm a teapot (IP Ban) | Transport-level HTTP status. Handled before app-level error classification. Indicates temporary IP ban — requires significant cooldown (hours). |
| 429 | Rate Limit Exceeded | Can 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-1022Re-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-9000Exponential 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
critical187031187049187040Sleep (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-1111300-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
high187055Find 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)
returnAd/Merchant Offline
medium8322983230Mark 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 interfaceDynamic Price Range
highParse 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 chosenUnknown Errors
lowConservative 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
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
| Code | Name | Category | Env Var |
|---|---|---|---|
| -1021 | Timestamp out of recvWindow | RETRY_WITH_SYNC | — |
| -1022 | Invalid signature | RETRY_WITH_SYNC | — |
| -1000 | Unknown error | RETRIABLE_WITH_BACKOFF | — |
| -9000 | System error (wrapper) | RETRIABLE_WITH_BACKOFF | — |
| -1102 | Mandatory parameter missing | NON_RETRIABLE_BUSINESS | — |
| -1104 | Unknown parameter | NON_RETRIABLE_BUSINESS | — |
| 187049 | Ad status error | RETRY_WITH_VOLUME_SYNC | — |
| 187040 | Ad status error (variant) | RETRY_WITH_VOLUME_SYNC | — |
| 187031 | Ad update failed | RETRY_WITH_VOLUME_SYNC | — |
| 187055 | Price overlap | SPECIAL_HANDLER | — |
| -2019 | Margin insufficient | NON_RETRIABLE_BUSINESS | — |
| -2015 | Invalid API key/permissions | NON_RETRIABLE_BUSINESS | — |
| -3026 | Order already completed | NON_RETRIABLE_BUSINESS | — |
| -1013 | Invalid quantity | NON_RETRIABLE_BUSINESS | — |
| -1111 | Precision over maximum | NON_RETRIABLE_BUSINESS | — |
| 83229 | Business closed | AD_OFFLINE | — |
| 83230 | Taking a break | AD_OFFLINE | — |