Cuatro rutas de lectura, sin clave y sin cookie, con los nombres de campo que los agregadores (CoinGecko, CoinMarketCap) y las bibliotecas de mercado (CCXT) esperan encontrar. Están hechas para robots: quien arma un panel, alimenta un índice o reconcilia un precio lee de aquí. No hay ninguna ruta de orden en esta API — nada de lo que está aquí mueve fondos ni exige cuenta.
Todo cuelga de https://offcode.pro/api/public/, solo acepta GET y responde application/json. El ticker_id es BASE_COTIZACION (BTC_USDT): base antes del guion bajo, cotización después, letras mayúsculas y dígitos, de 2 a 15 caracteres de cada lado. El par cuyo símbolo no cabe en ese formato no se publica.
Todo número viaja como cadena decimal, nunca como coma flotante — una moneda de ocho decimales pierde decimales al pasar por un float. Los instantes son milisegundos desde la época Unix.
| Ruta | Qué devuelve | Parámetros |
|---|---|---|
GET /pairs | Todo par que este feed direcciona, cada uno con su fuente de precio | — |
GET /tickers | Precio, volumen de 24 h, máximo, mínimo y ambas puntas de los pares spot | — |
GET /orderbook | Libro de órdenes de un par | ticker_id · depth |
GET /trades | Operaciones recientes de un par, de la más nueva a la más antigua | ticker_id · type · limit |
GET /historical_trades | Lo mismo que /trades — dos nombres, una implementación | ídem |
La lista de todo lo que este feed direcciona, y la puerta de las demás rutas: un ticker_id que no sale de aquí responde 404 en el libro y en las operaciones. El kind separa el mercado al contado de la casa (spot) del perpetuo (perp); cuando el mismo nombre sirve a los dos, el spot se queda con él. El price_source dice de dónde sale el número, en el propio ítem.
Precio, volumen de 24 horas, máximo, mínimo y ambas puntas de cada par spot. Los pares perp no entran: este es el recorte que describe el estándar de los agregadores.
ticker_id | El par, en formato BASE_COTIZACION. |
base_currency · target_currency | Las dos patas del par, por separado. |
last_price | Último precio negociado en el perpetuo. |
base_volume | Volumen de 24 h en moneda base, medido por la venue cuando ella lo publica. Solo en su ausencia la ruta divide target_volume entre last_price y corta a 8 decimales hacia abajo — un cálculo declarado, no un dato del exchange. |
target_volume | Volumen de 24 h en moneda de cotización, tal cual viene de la fuente. |
bid · ask | Iguales entre sí e iguales al mid del perpetuo — no son la cima del libro. Ver Fuente de los datos. |
high · low | Máximo y mínimo de las últimas 24 horas. |
timestamp | Instante de la lectura, en milisegundos desde la época Unix. |
El libro de órdenes de un par. bids y asks son pares [precio, tamaño], ambos cadenas decimales, del mejor precio al peor. El timestamp es el reloj de la fuente, no el nuestro: uno local dejaría que un libro viejo pasara por fresco.
depth es opcional y llega hasta 100 niveles por lado. Omitido, devuelve el techo: 100. Una petición mayor no se rechaza — se limita a 100.
Las operaciones recientes de un par, de la más nueva a la más antigua. Las dos rutas son la misma implementación y la misma caché: el estándar de los agregadores pide historical_trades, CCXT suele pedir trades, y así nunca existe el día en que una responde distinto de la otra.
type es opcional (buy o sell, por el lado agresor) y limit llega hasta 100, también limitado en vez de rechazado. Omitir type devuelve los dos lados, y omitir limit devuelve el techo: 100. El trade_id es derivado — instante, lado, precio y tamaño —, estable para la misma operación entre dos lecturas, y no es el identificador de la operación en la venue.
El precio, el libro, las operaciones y el volumen de 24 horas de este feed vienen del mercado perpetuo — incluso en los pares etiquetados spot. El spot de OFFCODE no tiene libro propio: cotiza el mid del perpetuo del mismo símbolo. Por eso cada ítem de /pairs lleva price_source: quien consume el feed lee la fuente en el propio feed, en vez de deducirla de la etiqueta — y deducirla mal.
ask − bid aquí da cero. Un spread cero es imposible en un libro real: no trate esos dos campos como las puntas de uno.base_volume es el volumen de 24 h en moneda base del perpetuo, medido por la venue cuando ella lo publica. Solo en su ausencia la ruta divide target_volume entre last_price y corta a 8 decimales hacia abajo — un cálculo declarado, no un dato del exchange.
Un par solo se publica donde existe un mercado negociable del mismo símbolo: publicar es prometer servir, y el ticker_id que sale de /pairs es el que el libro y las operaciones tienen que responder acto seguido. Una moneda en custodia sin par negociable queda fuera del feed, y lo mismo vale para el par cuyo nombre en la venue lleva factor — el perpetuo 1000PEPE_USDT se publica como sí mismo, nunca como PEPE_USDT.
600 solicitudes por minuto por IP, en cada ruta. Por encima de eso llega un 429.
Una respuesta 2xx sale con Cache-Control: public, max-age=5; lo que no es 2xx sale con no-store, porque un 404 guardado cinco segundos sobrevive al par recién listado, y un 502 guardado devuelve una venue caída después de que ya volvió.
Por dentro, el libro se relee como mucho cada 350 ms por par y profundidad, y las operaciones cada 1 segundo por par: el techo de idas a la venue es la caché, no el número de quien pregunta. Una lectura fallida nunca entra en la caché.
Access-Control-Allow-Origin: *, sin credenciales — no hay cookie, ni clave, ni cabecera de autenticación, y nada en la respuesta está personalizado.
| Código | Cuándo ocurre | Cuerpo |
|---|---|---|
| 400 | ticker_id fuera de formato, un type que no es buy ni sell, un depth o limit que no es entero | {"code": "VALIDATION_ERROR", "message": "…"} |
| 404 | ticker_id bien formado que no está en /pairs | {"error": "UNKNOWN_TICKER"} |
| 429 | Por encima del límite de solicitudes de esta IP | {"statusCode": 429, "code": "RATE_LIMITED", "message": "…"} |
| 502 | La venue no respondió, o respondió con fallo | {"code": "UPSTREAM_UNAVAILABLE", "message": "…"} |
502 y no 500, a propósito: un fallo de la venue no es un fallo nuestro, y quien lee desde fuera necesita esa diferencia para saber si vale la pena reintentar.
El dominio está detrás de Cloudflare. Un navegador pasa; una llamada de línea de comandos sin cookie puede recibir un desafío en lugar del JSON — lo que no es la ruta rechazando, sino el borde preguntando quién llegó. Si su robot necesita recorrer el feed de forma continua, pida la habilitación antes de encender el rastreo: indique el user-agent, los rangos de IP de salida y la frecuencia prevista en Contacto.
Las comisiones y las reglas de negociación no están en esta API: viven en /api/public/fees y en Comisiones. El estado de los servicios está en Estado de la plataforma.