Features y Quirks No Documentados
9 features no documentados y quirks de la API descubiertos mediante uso en producción. Son comportamientos que no encontrarás en ninguna documentación de Binance — solo mediante pruebas extensivas en el mundo real con más de 500K llamadas a la API.
Cambio Crítico 2026: Percent-Encoding para HMAC
A partir de 2026, Binance requiere percent-encoding de caracteres especiales en los valores del query string antes del cálculo HMAC. Los valores que contienen =,& o espacios deben estar URL-encoded antes de firmar. Anteriormente se aceptaban valores sin codificar. Este cambio fue poco publicitado y tomó por sorpresa a muchas integraciones.
1. Parámetro publisherType
Descubierto mediante ingeniería inversa de la UI web de Binance al activar el filtro "Verified Merchants". Funciona tanto en la búsqueda pública BAPI como en la búsqueda autenticada SAPI.
- Valores: "merchant" (solo verificados), omitido (todos)
- Sin él, algunos mercados devuelven userType: "user" para TODOS los anunciantes, incluidos los merchants
- Impacto: filtrar por userType post-respuesta excluye incorrectamente a merchants reales
2. Alcance de la Firma
La firma HMAC-SHA256 cubre ÚNICAMENTE los parámetros del query string. El body JSON NO se incluye en el cálculo de la firma.
- Consistente en todos los endpoints SAPI
- No está claramente documentado en el PDF oficial
- El body puede modificarse sin invalidar la firma
3. Nomenclatura adsNo vs advNo
Binance usa nombres de parámetros inconsistentes para los identificadores de anuncios en distintos endpoints.
- getDetailByNo → adsNo (query string)
- update → advNo (JSON body)
- updateStatus → advNos (plural, JSON body)
- Respuestas de búsqueda → advNo en los objetos de anuncio
4. Encapsulación de Errores de Negocio en HTTP 200
Binance encapsula errores de negocio (187049, 83229, etc.) en respuestas HTTP 200 con success: false o code != 0 en el body.
- SIEMPRE verifica el body de la respuesta, no solo el status HTTP
- success: false significa error aunque el HTTP sea 200
- code != "000000" indica una condición de error
5. Inestabilidad del Formato de updateStatus
El parámetro advNos a veces requiere un array JSON, a veces un string CSV. Este comportamiento varía y puede cambiar sin previo aviso.
- Intento 1: array JSON → advNos=["ad1", "ad2"]
- Si -1102: reintenta con CSV → advNos="ad1,ad2"
- Si sigue fallando: recurre a update_ad() individual por cada anuncio
6. Inestabilidad de Método/Parámetro en listUserOrderHistory
Este endpoint acepta diferentes métodos HTTP (GET/POST) y distintos nombres de parámetros de fecha según el entorno.
- Intento 1: GET con startTimestamp/endTimestamp
- Intento 2: GET con startTime/endTime (¡nomenclatura diferente!)
- Intento 3: POST con los mismos parámetros + body vacío
7. Header clientType: web
Requerido en la mayoría de endpoints SAPI C2C pero no documentado en el flujo estándar de autenticación. Sin él, algunos endpoints devuelven 403.
- Debe establecerse como header, no como parámetro de query string
- El valor siempre es el string "web"
- Omitirlo provoca fallos silenciosos en algunos endpoints
8. Stealth Headers para Búsqueda Pública
La búsqueda pública BAPI aplica soft rate limiting basado en los headers de la solicitud. Usar los valores por defecto de las librerías dispara respuestas vacías antes.
- Rota strings de User-Agent similares a los de un navegador
- Incluye headers Accept-Language y Referer
- El array de datos vacío es la señal de soft rate limit
9. Fallback de Entrega de Parámetros en getDetailByNo
Cuando enviar adsNo como parámetro de query string falla con -1102 o -1104, AutoP2P recurre a enviarlo en el body del POST.
- Principal: adsNo en query string (según PDF)
- Fallback en -1102: adsNo en body del POST
- El fallback también se activa en -1104 (Corrección de revisión #5)
- Detail404Tracker marca el anuncio como inactivo después de 3600s de 404s persistentes
publisherType en Acción
# 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
})