Saltar al contenido

API REST · v1

Documentación para desarrolladores

La API pública de OrigenOS: 16 operaciones sobre la carta, las comandas, las ventas, los clientes, el stock y la contabilidad del comercio, más webhooks salientes firmados. Autenticación por API key, respuestas JSON, mensajes de error en castellano.

Base URL
https://tu-comercio.origenos.com/api/v1
Autenticación
Authorization: Bearer ok_live_…
Formato
JSON · UTF-8 · fechas ISO-8601 (UTC)
Caudal
600 requests / 5 min por key

01 · En criollo

Qué es y para qué sirve

OrigenOS corre el mostrador: la carta, las comandas, la caja, el stock y los comprobantes fiscales. La API v1 es la puerta para que otro sistema tuyo —un ERP, un tablero de BI, una app de fidelidad, el bot de compras del proveedor, la web de pedidos que hiciste vos— lea esos datos y cargue pedidos, sin que nadie tenga que copiar números a mano de una pantalla a una planilla.

Es una API REST común y silvestre: pedís por HTTPS con una API key en el header, te contesta JSON. Cada key pertenece a un comercio y solo ve los datos de ese comercio: además del filtro en cada consulta, el aislamiento lo impone la base de datos con row-level security. Las fechas van y vuelven en ISO-8601, los importes son números en la moneda del comercio y los precios ya vienen con IVA adentro (como en el ticket).

Lo que la API no hace es tan importante como lo que hace: no crea ventas, no emite comprobantes fiscales, no mueve stock, no toca puntos de fidelidad ni asientos contables. Todo eso se calcula en el cierre de caja y en el motor contable, que son el único lugar donde salen bien los totales, el IVA, las promociones y el CFE. Escribir plata por una API paralela es la receta para que el cierre del café no cuadre con el sistema. Por eso solo hay dos caminos de escritura: crear comandas y crear o editar clientes.

02 · Autenticación

Conseguir una API key

Las keys las genera el dueño del comercio desde el panel. Vos, como integrador, no podés crearlas: pedísela y te la pasa por un canal seguro.

  1. PASO 1

    Activar el módulo

    La API es el add-on API Access + Webhooks. Se activa desde Módulos+ en la cuenta del comercio. Si no está activo, todas las llamadas responden 403 aunque la key exista.

  2. PASO 2

    Crear la key

    Un usuario administrador entra a Configuración → API & Webhooks (/configuracion/api), le pone un nombre y tilda los scopes que la integración necesita.

  3. PASO 3

    Guardarla ya

    La key en claro se muestra una sola vez. Después solo queda visible el prefijo (ok_live_a1b2) para identificarla. Si se pierde, se revoca y se crea otra.

Cómo se autentica

La key viaja en el header Authorization con el esquema Bearer. Nada de query params: una key en la URL termina en los logs del proxy, en el historial y en el Referer.

Primera llamadabash
curl "https://tu-comercio.origenos.com/api/v1/products?limit=5" \
  -H "Authorization: Bearer ok_live_ejemploNOsirve0000000000000000000"

Tratala como una contraseña

Una key da acceso de lectura —y según los scopes, de escritura— a los datos de un comercio real, incluidos datos personales de sus clientes. Guardala en variables de entorno, nunca en el repo ni en código de frontend: cualquiera que abra el navegador puede leerla. La API está pensada para llamarse desde tu servidor.

El host no elige el comercio

El comercio se resuelve a partir de la key, no del dominio. Usá el host del comercio (https://tu-comercio.origenos.com) por prolijidad y porque es el que te van a dar, pero no intentes cambiar de comercio cambiando el host: una key solo ve su propio comercio, siempre.

03 · Permisos

Los 8 scopes

Cada key lleva una lista de scopes y cada operación exige uno. Si la key no lo tiene, la respuesta es 403 con el nombre del scope que falta. Pedí solo los que uses: una key de lectura para el tablero no tiene por qué poder cargar pedidos.

Scopes disponibles al crear una API key
ScopeQué habilitaAlcance
products:readLeer la cartaProductos activos con precio, IVA, categoría y dónde se preparan.
orders:readLeer comandasComandas abiertas y cerradas, con sus ítems.
orders:writeCrear comandasCargar pedidos desde afuera. Entran como pendientes de aprobación.
sales:readLeer ventasVentas cerradas con total, IVA, medios de pago y datos del comprobante fiscal.
customers:readLeer clientesFicha de clientes y socios, con sus puntos.
customers:writeCrear y editar clientesAlta y actualización de clientes desde afuera.
stock:readLeer stockInsumos con existencia actual, mínimo y movimientos.
accounting:readLeer contabilidadPlan de cuentas y asientos contables.

Por qué no hay escritura de plata ni de fiscal

No existe sales:write ni accounting:write, y no es un olvido: es una decisión de diseño. Una venta se cierra en la caja, que es donde se resuelven totales, IVA, promociones, puntos y comprobante fiscal en un solo lugar. Una API que también pudiera crear ventas tendría una segunda fórmula, y dos fórmulas de plata siempre terminan dando números distintos.

04 · Referencia

Las 16 operaciones

Esto es todo lo que existe hoy en /api/v1. No hay endpoints ocultos ni beta: si no está en esta tabla, no está en la API. Todos los listados devuelven data con los resultados; el bloque de paginación cambia según el recurso (lo explicamos abajo).

Carta

Lo que el comercio vende hoy. Solo lectura: la carta se edita en OrigenOS. Devuelve productos activos y NO archivados.

Operaciones de carta
MétodoRutaScopeQué haceParámetros
GET/api/v1/productsproducts:readLista los productos de la carta, en el orden que les dio el dueño, con precio de mostrador, IVA, categoría y dónde se preparan.limit (1–200, default 100), cursor, categoryId, categorySlug, available, isCombo
GET/api/v1/categoriesproducts:readLista las secciones de la carta con su orden y cuántos productos vigentes tiene cada una. Sin esto no se puede armar un menú ordenado.limit (1–200, default 100), cursor, active (default true)

Comandas

El pedido antes de cobrarse. Es el único recurso de la API con escritura: se pueden crear comandas desde afuera.

Operaciones de comandas
MétodoRutaScopeQué haceParámetros
GET/api/v1/ordersorders:readLista comandas abiertas y cerradas, de la más nueva a la más vieja. La lista va liviana: sin líneas de detalle.limit (1–100, default 50), cursor, status, locationId, from / to (alias desde / hasta)
POST/api/v1/ordersorders:writeCarga una comanda nueva. Entra como PENDING_APPROVAL: alguien la aprueba desde el POS antes de que vaya a cocina.body: channel, locationId, customerName, customerPhone, notes, items[] · header Idempotency-Key
GET/api/v1/orders/{id}orders:readDetalle de una comanda con sus líneas vigentes, las anuladas por separado y el subtotal bruto ya calculado.

Ventas

La plata ya cobrada, con su comprobante fiscal. Solo lectura, siempre: los totales, el IVA, las promos y el CFE se calculan en el cierre de caja, que es el único camino canónico.

Operaciones de ventas
MétodoRutaScopeQué haceParámetros
GET/api/v1/salessales:readLista ventas cerradas con totales, medio de pago dominante y bloque fiscal. Por defecto excluye las anuladas y mira los últimos 30 días.limit (1–200, default 50), cursor, from / to (default 30 días, máximo 366), locationId, voided (false | true | all)
GET/api/v1/sales/{id}sales:readDetalle de una venta: líneas, desglose real por medio de pago (pago dividido), datos del receptor del CFE y el árbol de notas de crédito.

Clientes

La ficha de clientes y socios. Es el otro recurso con escritura, y el que más cuidado pide: son datos personales de gente real.

Operaciones de clientes
MétodoRutaScopeQué haceParámetros
GET/api/v1/customerscustomers:readLista clientes con búsqueda por nombre, teléfono, email o documento. El documento sale enmascarado.limit (1–100, default 50), cursor, q (mínimo 2 caracteres), active
POST/api/v1/customerscustomers:writeDa de alta un cliente. El documento lo valida el driver del país del comercio. Email y documento duplicados dan 409.body: name (obligatorio), phone, email, documentId, birthday, notes, marketingConsent
GET/api/v1/customers/{id}customers:readFicha completa: documento sin enmascarar, notas, consentimiento de marketing y saldo de puntos (solo lectura).
PATCH/api/v1/customers/{id}customers:writeActualización parcial: solo se tocan los campos que mandás. Un null borra el dato; un campo ausente lo deja como está.body: cualquiera de los campos del alta (al menos uno)

Stock

Insumos y su existencia. Solo lectura: la API no mueve stock. El caso de uso típico es un ERP que pregunta cada tanto qué hay que reponer.

Operaciones de stock
MétodoRutaScopeQué haceParámetros
GET/api/v1/stockstock:readLista insumos con existencia, mínimo, costo por unidad de compra y de uso, valorización y proveedor preferido.limit (1–200, default 100), cursor, belowMin
GET/api/v1/stock/{id}/movementsstock:readMovimientos de un insumo: compras, consumo, mermas, ajustes y despieces, con signo y costo. Incluye la cabecera del insumo.limit (1–200, default 100), cursor, type, from / to (alias desde / hasta)

Contabilidad

Plan de cuentas y libro diario, para el estudio contable o el BI. Solo lectura: los asientos los genera el motor contable.

Operaciones de contabilidad
MétodoRutaScopeQué haceParámetros
GET/api/v1/accounting/accountsaccounting:readPlan de cuentas jerárquico. Cada cuenta trae parentId y parentCode para rearmar el árbol del lado tuyo.limit (default 500, máximo 2000), type, kind, active, country (ISO alpha-2)
GET/api/v1/accounting/entriesaccounting:readLibro diario del período. Los asientos anulados vienen marcados. Los totales son los persistidos, no se recalculan.from y to (YYYY-MM-DD, OBLIGATORIOS, máximo 366 días), limit (default 50, máximo 200), cursor, source, voided, accountId
GET/api/v1/accounting/entries/{id}accounting:readAsiento con todas sus líneas. Devuelve los totales persistidos, la suma de las líneas por separado y una bandera de integridad.

Rangos de fecha

En todos los listados que aceptan from / to (y sus alias en castellano desde / hasta), from es inclusivo y to es exclusivo. Una fecha suelta como 2026-07-01 se lee como medianoche UTC: si querés cortar por el día local del comercio, mandá el offset (2026-07-01T00:00:00-03:00). No adivinamos el huso horario, y una fecha que no existe —2026-02-31— da 400 en vez de correrse sola a marzo.

En /api/v1/accounting/entries el formato es distinto y más estricto: from y to son obligatorios y solo aceptan YYYY-MM-DD. En contabilidad una fecha ambigua te corre un asiento de mes.

05 · Los dos casos de siempre

Ejemplos completos

Listar comandas

El listado va liviano a propósito: trae la cabecera de cada comanda, sin las líneas. Para el detalle con ítems pegale a /api/v1/orders/{id}.

Requestbash
curl -G "https://tu-comercio.origenos.com/api/v1/orders" \
  -H "Authorization: Bearer ok_live_ejemploNOsirve0000000000000000000" \
  --data-urlencode "status=IN_KITCHEN" \
  --data-urlencode "from=2026-07-28T00:00:00-03:00" \
  --data-urlencode "to=2026-07-29T00:00:00-03:00" \
  --data-urlencode "limit=2"
Response · 200json
{
  "data": [
    {
      "id": "cmd8f3k2p0001qz8v9h2b7n4m",
      "number": 1042,
      "channel": "TAKE_AWAY",
      "status": "IN_KITCHEN",
      "locationId": "loc8a1b2c0000qz8v0d1e2f3g",
      "customer": { "name": "Lucía", "phone": "099123456" },
      "notes": "Pedido vía API",
      "createdAt": "2026-07-28T14:03:11.482Z",
      "closedAt": null
    },
    {
      "id": "cmd8f2r9x0000qz8v4k7c1p2s",
      "number": 1041,
      "channel": "MESA",
      "status": "IN_KITCHEN",
      "locationId": "loc8a1b2c0000qz8v0d1e2f3g",
      "customer": { "name": null, "phone": null },
      "notes": null,
      "createdAt": "2026-07-28T13:58:02.117Z",
      "closedAt": null
    }
  ],
  "pagination": {
    "limit": 2,
    "hasMore": true,
    "nextCursor": "cmd8f2r9x0000qz8v4k7c1p2s"
  }
}

Crear una comanda

Los precios no se mandan: los pone el sistema desde la carta, con el precio de mostrador vigente. Vos mandás qué producto y cuánto. La comanda entra con estado PENDING_APPROVAL y aparece en la pantalla de comandas para que alguien la apruebe antes de que vaya a cocina.

Requestbash
curl -X POST "https://tu-comercio.origenos.com/api/v1/orders" \
  -H "Authorization: Bearer ok_live_ejemploNOsirve0000000000000000000" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-2026-07-28-0042" \
  -d '{
    "channel": "TAKE_AWAY",
    "customerName": "Lucía",
    "customerPhone": "099123456",
    "notes": "Para llevar, sin azúcar",
    "items": [
      { "productId": "prd7k1m4v0002qz8v8s3d9f0a", "quantity": 2, "notes": "bien caliente" },
      { "productId": "prd7k1m4v0007qz8v2b6h4j1k", "quantity": 1 }
    ]
  }'
Response · 201json
HTTP/1.1 201 Created

{
  "data": {
    "id": "apiord_5rQ2hK9tYcVb1nD7wLp3sXe",
    "number": 1043,
    "channel": "TAKE_AWAY",
    "status": "PENDING_APPROVAL",
    "locationId": "loc8a1b2c0000qz8v0d1e2f3g",
    "customer": { "name": "Lucía", "phone": "099123456" },
    "notes": "Para llevar, sin azúcar",
    "createdAt": "2026-07-28T14:11:47.903Z",
    "closedAt": null,
    "items": [
      {
        "id": "itm8f4a1b0001qz8v7m2n5q8r",
        "name": "Flat white",
        "quantity": 2,
        "unitPrice": 190,
        "vatRate": 22,
        "status": "PENDING",
        "chargeable": true,
        "station": "BAR",
        "workStationId": "wst3c9d2e0000qz8v1f4g7h2j",
        "comboChild": false,
        "parentItemId": null,
        "notes": "bien caliente",
        "voidReason": null
      },
      {
        "id": "itm8f4a1b0002qz8v3t6y9u1i",
        "name": "Medialuna de manteca",
        "quantity": 1,
        "unitPrice": 90,
        "vatRate": 22,
        "status": "PENDING",
        "chargeable": true,
        "station": "KITCHEN",
        "workStationId": null,
        "comboChild": false,
        "parentItemId": null,
        "notes": null,
        "voidReason": null
      }
    ],
    "voidedItems": [],
    "itemsSummary": {
      "chargeableCount": 2,
      "voidedCount": 0,
      "subtotalGross": 470
    }
  },
  "replayed": false
}

Idempotency-Key: mandala siempre

El header Idempotency-Key es opcional, pero si no lo mandás y se te vence un timeout, el reintento carga el pedido dos veces y la cocina prepara dos desayunos. Con la clave puesta, el segundo intento devuelve la comanda original con 200, "replayed": true y el header Idempotent-Replay: true — y no vuelve a disparar el webhook, porque no pasó nada nuevo. La clave tiene que tener entre 8 y 200 caracteres ASCII imprimibles sin espacios (un UUID, un ULID o algo como pedido-2026-07-28-0042).

Detalle importante: gana la clave, no el contenido. Si reusás la misma clave con ítems distintos, te devolvemos la comanda vieja en vez de crear la nueva. Es conservador a propósito: ante la duda, no duplicamos comida. Una clave nueva por pedido y listo.

Sobre los ítems anulados

En el detalle de una comanda, items trae solo las líneas vigentes y voidedItems las anuladas, con su motivo. Están separadas porque si van todas juntas, quien suma cantidad × precio cobra de más: la línea que el mozo anuló seguiría sumando. itemsSummary.subtotalGross ya viene calculado con la fórmula del cierre.

Ese subtotal es el bruto de las líneas, no el total del ticket. Los descuentos, las promociones, los puntos y el comprobante fiscal se resuelven recién al cobrar. Por eso la API no expone un "total" de comanda: si lo hiciera, sería un número que no coincide con lo que se cobra.

06 · Recorrer listados

Paginación

Todos los listados paginan por cursor (nunca por offset: con offset, una fila nueva corre todo y te saltea resultados). Pedís una página, te devolvemos un cursor, y se lo pasás tal cual a la request siguiente repitiendo los mismos filtros. Cuando el cursor viene en null, se terminó.

La forma del bloque de paginación no es uniforme entre recursos —te lo decimos de frente para que no te sorprenda:

Dónde viene el cursor en cada listado
ListadoEnvoltorio de la respuestaDónde está el próximo cursor
products, categories, orders, stock, stock/{id}/movements{ data, pagination }pagination.nextCursor (+ pagination.hasMore)
sales{ data, pagination, range, filters }pagination.nextCursor · range devuelve la ventana realmente aplicada
customers{ data, nextCursor }nextCursor, en la raíz (no hay bloque pagination)
accounting/entries{ data, meta }meta.nextCursor (+ meta.hasMore)
accounting/accounts{ data, meta }No pagina. Subí limit (hasta 2000) y mirá meta.truncated

Dos detalles que ahorran una tarde

El cursor es opaco. En algunos recursos es el id de la última fila y en otros un valor codificado en base64url. No lo interpretes ni lo construyas a mano: copialo tal cual. Un cursor malformado siempre da 400; en carta, comandas, stock y clientes se valida además que corresponda a un registro de tu comercio, así que un id prestado corta con 400 en vez de devolver una página vacía sin explicación.

Un limit inválido no se comporta igual en todos lados. En carta, comandas y stock, un valor fuera de rango da 400 con el nombre del parámetro. En ventas, clientes y contabilidad se recorta en silencio al rango permitido. Mandá siempre un entero dentro del rango y no dependas de ninguno de los dos comportamientos.

07 · Cuando algo sale mal

Códigos de error

Los errores vienen con un campo error en castellano, pensado para que se pueda leer en un log sin traducir nada. Cuando el problema es un parámetro puntual, se agrega param; en los choques de duplicado, field.

Respuestas de error de /api/v1
CódigoCuándoBody
400Un parámetro no válido, un body que no parsea, un cursor que no es de tu comercio o un combo en POST /orders.{ "error": "…", "param": "limit" }
401Falta el header Authorization, o la key no existe / fue revocada.{ "error": "API key inválida o revocada" }
403La key no tiene el scope que pide la operación, o el add-on API Access no está activo en el comercio.{ "error": "La API key no tiene el scope \"orders:write\"" }
404El id no existe en tu comercio. Nunca decimos si existe en otro: desde afuera no se puede distinguir.{ "error": "Orden no encontrada" }
409Email o documento de cliente duplicado, o una Idempotency-Key ya usada por otra comanda.{ "error": "Ya hay un cliente de este comercio con ese email.", "field": "email" }
429Pasaste el límite de caudal de la key. Ojo: este body tiene forma distinta al resto.{ "ok": false, "error": "Demasiados intentos…", "retryAfterSec": 300 }

El 429 tiene otra forma

Todos los errores usan { "error": "…" } salvo el del límite de caudal, que devuelve { "ok": false, "error": "…", "retryAfterSec": n }. Si tu cliente parsea errores, contemplá los dos formatos.

08 · Eventos salientes

Webhooks

En vez de preguntar cada treinta segundos si pasó algo, dejás una URL https y te avisamos nosotros. Las suscripciones se crean en el mismo panel que las keys (/configuracion/api), eligiendo a qué eventos querés escuchar. Al crearla se muestra un secret (whsec_…) una sola vez: con ese secret se verifica la firma.

Eventos

Eventos a los que se puede suscribir una URL
EventoQué significaEstado
order.createdSe cargó una comanda nueva.Se emite al crear una comanda por POST /api/v1/orders. El data incluye los ítems.
order.closedSe cobró y cerró una comanda.Se emite al cobrar una comanda en la caja. El data trae solo la cabecera, sin ítems: si necesitás el detalle, pedí /api/v1/orders/{id}.
order.cancelledSe anuló una comanda.Se puede seleccionar en el panel, pero hoy no lo emite ningún camino del sistema. No construyas lógica esperándolo.

Cómo llega

Un POST con Content-Type: application/json y dos headers propios: X-OrigenOS-Event con el nombre del evento y X-OrigenOS-Signature con la firma. El cuerpo siempre tiene la misma forma: el evento, el dato y un deliveryId —que te sirve para deduplicar si te llega repetido.

Entregahttp
POST https://tu-erp.example.com/hooks/origenos
Content-Type: application/json
X-OrigenOS-Event: order.created
X-OrigenOS-Signature: sha256=6f1c…c0ffee

{
  "event": "order.created",
  "data": { "id": "apiord_5rQ2…", "number": 1043, "status": "PENDING_APPROVAL", "…": "…" },
  "deliveryId": "whd8g5h2i0003qz8v6k1l4m7n"
}

Verificar la firma (HMAC-SHA256)

La firma es sha256= seguido del HMAC-SHA256 del cuerpo crudo usando el secret de la suscripción como clave. Verificala siempre: sin eso, cualquiera que adivine tu URL puede inventarte pedidos.

Verificaciónjavascript
import crypto from "node:crypto";

// El HMAC se calcula sobre el CUERPO CRUDO, byte por byte. Si lo parseás y lo
// volvés a serializar, la firma no va a coincidir: guardate el raw body.
export function firmaValida(rawBody, headerFirma, secret) {
  const esperado =
    "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");

  const a = Buffer.from(esperado, "utf8");
  const b = Buffer.from(headerFirma ?? "", "utf8");

  // Comparación en tiempo constante: un === filtra el secreto de a poquito.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Ejemplo con Express (nota el express.raw: NO uses express.json acá).
app.post(
  "/hooks/origenos",
  express.raw({ type: "application/json" }),
  (req, res) => {
    if (!firmaValida(req.body, req.get("X-OrigenOS-Signature"), process.env.OS_WEBHOOK_SECRET)) {
      return res.sendStatus(401);
    }
    const evento = JSON.parse(req.body.toString("utf8"));
    // Respondé 2xx rápido y procesá después: 10 segundos y cortamos.
    res.sendStatus(200);
    encolar(evento);
  },
);

Reintentos

  • Se considera entregado con cualquier respuesta 2xx. Todo lo demás —incluido un timeout— cuenta como fallo.
  • Cada intento espera 10 segundos como máximo. Contestá rápido y procesá después: si tardás, lo tomamos por caído y reintentamos.
  • Hasta 5 intentos en total, con backoff de 1 minuto, 5 minutos, 30 minutos y 2 horas. Después del quinto se abandona la entrega.
  • Los reintentos los levanta un proceso que corre cada 5 minutos, así que el próximo intento puede caer un poco después del backoff exacto.
  • La URL tiene que ser https y pública: se bloquean destinos internos (protección contra SSRF). Un localhost o una IP privada no van a recibir nada.
  • Hacé tu endpoint idempotente. Con reintentos de por medio, un mismo deliveryId puede llegarte más de una vez; guardá cuáles ya procesaste.

09 · Cuotas

Límite de caudal

600 requests cada 5 minutos por API key (unas 2 por segundo sostenidas). El contador es por key, no por IP ni por endpoint: todas las rutas de /api/v1 comparten el mismo balde. Si tenés varias integraciones, dales keys distintas y no se pisan entre ellas.

Al pasarte, la API responde 429 con el header Retry-After en segundos. Los rechazos no cuentan como consumo, así que la ventana no se renueva sola por seguir golpeando: esperá lo que dice Retry-After y reintentá.

Respuesta al pasarsehttp
HTTP/1.1 429 Too Many Requests
Retry-After: 300

{
  "ok": false,
  "error": "Demasiados intentos. Esperá un momento y volvé a probar.",
  "retryAfterSec": 300
}

Cómo no llegar al tope

Sincronizá la carta cada tanto y guardala en caché en tu lado (no cambia todo el día). Para enterarte de comandas nuevas usá webhooks en vez de encuestar el listado. Y al recorrer históricos, subí el limit en lugar de hacer más requests: 200 ventas en una llamada gastan lo mismo que 20.

10 · Spec

OpenAPI

OrigenOS publica el spec OpenAPI 3.1 de esta API en /api/openapi (YAML). Trae las 16 operaciones de /api/v1, los webhooks salientes y los esquemas de todos los recursos. Podés descargarlo con curl y abrirlo en Swagger Editor, Postman o Insomnia, o generar un cliente con él.

Hay un segundo spec, y no es este

/api/openapi?spec=internal sirve un archivo distinto, que documenta otra superficie del producto: el pedido por QR en la mesa, el programa de fidelidad, el webhook de WhatsApp, los procesos programados y el webhook de Stripe. No es la API que estás integrando y no incluye ninguna ruta de /api/v1. Lo dejamos publicado para no romperle el link a quien ya lo tenía.

11 · Sin vueltas

Limitaciones conocidas

Todo esto lo sabemos y lo decimos acá para que no lo descubras a las tres de la tarde de un viernes. Si algo de la lista te bloquea, escribinos: sirve para priorizar.

Los combos no se cargan por API

POST /api/v1/orders rechaza con 400 cualquier ítem que sea un combo, y devuelve sus nombres en el campo combos del error.

Un combo no es una línea: es una cabecera con el precio final más un hijo por cada opción elegida, cada uno con su estación de impresión y su receta. Sin las selecciones del cliente entraría una línea plana, sin hijos y sin descontar recetas —cocina no sabría qué preparar y el comercio cobraría de menos. Preferimos el error explícito antes que la comanda rota en silencio. Los combos se cargan desde el POS. En GET /api/v1/products se los reconoce por isCombo: true y traen el resumen de sus grupos.

No se puede modificar ni cancelar una comanda

Solo hay alta. No existe PATCH ni DELETE sobre /api/v1/orders: agregar ítems, anular líneas, cerrar o anular una comanda se hace desde el POS.

order.cancelled no se dispara

El evento se puede tildar al crear una suscripción, pero hoy ningún camino del sistema lo emite. Los que sí llegan son order.created y order.closed.

No hay endpoint de entregas de webhook

Si tu servidor estuvo caído, no hay forma de listar ni de reenviar las entregas fallidas por API. Los reintentos son automáticos (5 intentos, hasta ~2h 36m de ventana) y después se pierden.

La idempotencia mira la clave, no el body

Reusar una Idempotency-Key con ítems distintos devuelve la comanda original en lugar de crear la nueva, y no avisa que el contenido cambió. Usá una clave nueva por pedido.

Los puntos de fidelidad son solo lectura

El saldo se ve en la ficha del cliente (GET /api/v1/customers/{id}) pero no hay forma de acreditarlos ni canjearlos por API: los mueve únicamente el motor de fidelidad. El listado tampoco los trae —el único número barato de listar sería un espejo que el motor nuevo no actualiza, o sea un dato falso.

Campos de cliente que la API no toca

No se puede activar ni desactivar un cliente (dar de baja tiene un control de cuenta corriente que vive en el sistema), ni tocar fiado, límites de crédito o datos fiscales. Un cliente dado de baja no se lista, no se lee y no se edita: responde 404.

Los campos desconocidos en el body dan 400 en vez de ignorarse, así que un typo se nota enseguida.

Ventanas de consulta acotadas

Ventas: máximo 366 días por consulta y, si no mandás from, se asumen los últimos 30. Asientos contables: from y to obligatorios, tope de 366 días. Para históricos largos, partí en tramos y paginá.

El plan de cuentas no pagina

GET /api/v1/accounting/accounts devuelve hasta limit filas (máximo 2000) y avisa con meta.truncated: true si cortó. No hay cursor: si te truncan, el árbol queda con padres faltantes.

No hay SDKs todavía

Los SDKs oficiales de TypeScript y Python están anunciados pero no publicados. Por ahora, fetch, requests o lo que uses.

No hay entorno de pruebas separado

Todas las keys son ok_live_: apuntan a datos reales. Si vas a probar la creación de comandas, coordiná con el comercio y usá una sucursal o un horario donde no moleste.

Precios de plataformas de delivery

price es siempre el precio de mostrador. Los precios diferenciados de PedidosYa y Rappi no se exponen: son un canal aparte y se administran en el sistema.

Ayuda

¿Te trabaste con algo?

Si un endpoint devuelve algo distinto a lo que dice esta página, es un error nuestro y lo queremos saber. Escribinos con la ruta, los parámetros y el prefijo de la key (nunca la key completa) y lo miramos.