OFFCODEDOCSAbrir la plataforma

API pública

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.

Base y convenciones

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.

RutaQué devuelveParámetros
GET /pairsTodo par que este feed direcciona, cada uno con su fuente de precio
GET /tickersPrecio, volumen de 24 h, máximo, mínimo y ambas puntas de los pares spot
GET /orderbookLibro de órdenes de un particker_id · depth
GET /tradesOperaciones recientes de un par, de la más nueva a la más antiguaticker_id · type · limit
GET /historical_tradesLo mismo que /trades — dos nombres, una implementaciónídem

Pares — /pairs

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.

GET /api/public/pairs
[
  { "ticker_id": "BTC_USDT", "base": "BTC", "target": "USDT", "kind": "spot", "price_source": "offcode-perp" },
  { "ticker_id": "1000PEPE_USDT", "base": "1000PEPE", "target": "USDT", "kind": "perp", "price_source": "offcode-perp" }
]

Tickers — /tickers

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.

GET /api/public/tickers
[
  {
    "ticker_id": "BTC_USDT", "base_currency": "BTC", "target_currency": "USDT",
    "last_price": "…", "base_volume": "…", "target_volume": "…",
    "bid": "…", "ask": "…", "high": "…", "low": "…", "timestamp": 1756800000000
  }
]
ticker_idEl par, en formato BASE_COTIZACION.
base_currency · target_currencyLas dos patas del par, por separado.
last_priceÚltimo precio negociado en el perpetuo.
base_volumeVolumen 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_volumeVolumen de 24 h en moneda de cotización, tal cual viene de la fuente.
bid · askIguales entre sí e iguales al mid del perpetuo — no son la cima del libro. Ver Fuente de los datos.
high · lowMáximo y mínimo de las últimas 24 horas.
timestampInstante de la lectura, en milisegundos desde la época Unix.

Libro — /orderbook

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.

GET /api/public/orderbook?ticker_id=BTC_USDT&depth=100
{
  "ticker_id": "BTC_USDT",
  "timestamp": 1756800000000,
  "bids": [["…", "…"], ["…", "…"]],
  "asks": [["…", "…"], ["…", "…"]]
}

Operaciones — /trades y /historical_trades

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.

GET /api/public/trades?ticker_id=BTC_USDT&type=buy&limit=100
[
  { "trade_id": "…", "price": "…", "base_volume": "…", "target_volume": "…", "trade_timestamp": 1756800000000, "type": "buy" }
]

Fuente de los datos

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.

bid y ask son el mismo mid. No hay libro spot propio del que sacar una cima de libro, así que 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.

Límites, caché y CORS

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.

Errores

CódigoCuándo ocurreCuerpo
400ticker_id fuera de formato, un type que no es buy ni sell, un depth o limit que no es entero{"code": "VALIDATION_ERROR", "message": "…"}
404ticker_id bien formado que no está en /pairs{"error": "UNKNOWN_TICKER"}
429Por encima del límite de solicitudes de esta IP{"statusCode": 429, "code": "RATE_LIMITED", "message": "…"}
502La 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.

Agregadores y acceso automatizado

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.