Undocumented Features & Quirks
9 undocumented features and API quirks discovered through production usage. These are behaviors you won't find in any Binance documentation — only through extensive real-world testing with 500K+ API calls.
2026 Breaking Change: Percent-Encoding for HMAC
Starting in 2026, Binance requires percent-encoding of special characters in query string values before HMAC computation. Values containing =,&, or spaces must be URL-encoded before signing. Previously, raw values were accepted. This change was poorly publicized and caught many integrations off-guard.
1. publisherType Parameter
Discovered by reverse-engineering Binance's web UI when toggling the "Verified Merchants" filter. Works on both the public BAPI search AND the authenticated SAPI search.
- Values: "merchant" (verified only), omitted (all)
- Without it, some markets return userType: "user" for ALL advertisers including merchants
- Impact: filtering by userType post-response incorrectly excludes real merchants
2. Signature Scope
The HMAC-SHA256 signature covers ONLY the query string parameters. The JSON body is NOT included in the signature computation.
- Consistent across all SAPI endpoints
- Not clearly documented in official PDF
- Body can be modified without invalidating the signature
3. adsNo vs advNo Naming
Binance uses inconsistent parameter naming for ad identifiers across different endpoints.
- getDetailByNo → adsNo (query string)
- update → advNo (JSON body)
- updateStatus → advNos (plural, JSON body)
- Search responses → advNo in ad objects
4. HTTP 200 Business Error Wrapping
Binance wraps business errors (187049, 83229, etc.) in HTTP 200 responses with success: false or code != 0 in the body.
- ALWAYS check the response body, not just HTTP status
- success: false means error even with HTTP 200
- code != "000000" indicates an error condition
5. updateStatus Format Instability
The advNos parameter sometimes requires a JSON array, sometimes a CSV string. This behavior varies and can change without notice.
- Try 1: JSON array → advNos=["ad1", "ad2"]
- If -1102: retry with CSV → advNos="ad1,ad2"
- If still fails: fall back to individual update_ad() per ad
6. listUserOrderHistory Method/Param Instability
This endpoint accepts different HTTP methods (GET/POST) and different date parameter names depending on the environment.
- Try 1: GET with startTimestamp/endTimestamp
- Try 2: GET with startTime/endTime (different naming!)
- Try 3: POST with same params + empty body
7. clientType: web Header
Required on most SAPI C2C endpoints but not documented in the standard auth flow. Without it, some endpoints return 403.
- Must be set as a header, not a query parameter
- Value is always the string "web"
- Omitting it causes silent failures on some endpoints
8. Stealth Headers for Public Search
The public BAPI search applies soft rate limiting based on request headers. Using standard library defaults triggers empty responses sooner.
- Rotate browser-like User-Agent strings
- Include Accept-Language and Referer headers
- Empty data array is the soft rate limit signal
9. getDetailByNo Param Delivery Fallback
When sending adsNo as a query string parameter fails with -1102 or -1104, AutoP2P falls back to sending it in the POST body.
- Primary: adsNo in query string (per PDF)
- Fallback on -1102: adsNo in POST body
- Fallback also triggers on -1104 (Review correction #5)
- Detail404Tracker marks ad dead after 3600s of persistent 404s
publisherType in Action
# Without publisherType — may get userType: "user" for ALL ads
resp = await client.post(url, json={
"asset": "USDT", "fiat": "USD", "tradeType": "BUY"
})
# With publisherType — correctly filters verified merchants
resp = await client.post(url, json={
"asset": "USDT", "fiat": "USD", "tradeType": "BUY",
"publisherType": "merchant" # Undocumented but critical
})