Skip to main content
La API pública de Commercy es una API REST en JSON. Todos los ejemplos de esta página usan la versión 2026-10.

Versionado

La versión va en la URL y es una fecha (2026-10). Una versión nueva se publica como otro prefijo, sin romper la anterior, y cada versión se mantiene al menos 12 meses después de que sale la siguiente. Una versión inexistente responde 404 not_found.

Convenciones

  • Los nombres de campo son snake_case.
  • Los ids son strings.
  • Los montos son números en pesos argentinos y los recursos con importes traen "currency": "ARS".
  • Las fechas son ISO 8601 en UTC, por ejemplo 2026-10-09T12:00:00.000Z.
  • Los campos sin valor vienen como null. Los campos de costo sólo aparecen si tu app tiene el scope read_costs.
  • Los parámetros y campos desconocidos se rechazan con 400 invalid_request (no se ignoran en silencio), para que no creas que aplicó un filtro que no existe.
  • Todas las respuestas traen el header X-Request-Id; mandalo a soporte si necesitás ayuda con un request puntual.
  • Un recurso de otro comercio responde 404 not_found, igual que uno que no existe. Nunca confirmamos que existe.

Respuestas

Un recurso:
Una colección:
Los errores tienen su propio formato, descripto en Errores.

Paginación

Las colecciones se paginan con cursor. Parámetros: Mientras next_cursor no sea null, hay más páginas. El cursor es opaco y está atado a los filtros con los que se pidió: si lo usás con otros filtros recibís 400 invalid_request. Las listas de productos, ventas y clientes se ordenan por fecha de modificación ascendente, así que podés sincronizar de forma incremental guardando la última fecha vista y pidiendo updated_since. updated_since y created_since son fechas ISO 8601 e incluyen el instante indicado (>=).

Idempotencia

Mandá un header Idempotency-Key (1 a 255 caracteres ASCII imprimibles, por ejemplo un UUID) para que un reintento no repita la operación.
  • Es obligatorio en POST /stock/adjustments y opcional en el resto de los POST, PUT y PATCH. DELETE no usa idempotencia.
  • La clave vale 24 horas y se guarda por instalación.
  • Repetir la misma clave con el mismo body devuelve la respuesta original, con el header Idempotent-Replayed: true.
  • La misma clave con otro body responde 422 idempotency_conflict.
  • Si el primer request todavía está en curso (hasta 60 segundos) responde 409 conflict: esperá y reintentá.
  • Las respuestas 5xx no se guardan, así que podés reintentar con la misma clave.

Tienda

GET /store

Sin scope. Datos del comercio donde está instalada la app.
plan_tier es professional, enterprise, demo o developer.

Sucursales

Scope read_shops.

GET /shops y GET /shops/:id

La sucursal que no pertenece al comercio responde 404.

Categorías y marcas

Scope read_products. GET /categories y GET /brands aceptan limit y cursor.
Las marcas traen id, name, slug, is_active, image y updated_at.

Productos

Scope read_products.

GET /products

No se listan los productos en borrador. Un category o brand que no es del comercio responde 404.

GET /products/:id

Devuelve un producto con sus variantes.
price es el precio base. Para el precio que realmente paga un cliente en una sucursal y lista de precios usá GET /prices. Con el scope read_costs el producto trae además cost, y cada variante su propio cost.

Precios

Scope read_prices.

GET /prices

Precio resuelto de cada producto y variante en una sucursal, con el mismo motor que usa el punto de venta.
price_list_id solo viene informado si el comercio usa listas de precios. source indica de dónde sale el precio resuelto.

Stock

Scope read_stock.

GET /stock

Una fila por producto o, si tiene variantes, por variante.
on_hand es el stock físico y available lo que se puede vender (descontadas las reservas). Nunca se exponen lotes, costos ni proveedores.

Ventas

Scope read_orders. Sólo se listan las ventas del comercio; las compras a proveedores no aparecen.

GET /orders

GET /orders/:id

El :id acepta el id de la venta o su order_number.
Con el scope read_costs cada línea trae además cogs (costo de la mercadería vendida). customer es null si la venta no tiene un comprador identificable.

Clientes

Scope read_customers. Sólo los clientes del comercio: no incluye al equipo ni a los contactos que todavía no son clientes.

GET /customers y GET /customers/:id

document solo viene informado para un DNI. Los datos fiscales (CUIT, CUIL) nunca se exponen.

Escrituras

Todas requieren el scope write_* correspondiente y devuelven el recurso actualizado en el mismo formato que la lectura. Los POST que crean un recurso responden 201.

Productos (write_products)

  • POST /products crea un producto. No acepta precio ni costo (ni en el producto ni en sus variantes): la API responde 400 si los mandás. El producto nace con precio 0 y visibilidad interna; asignale el precio con PUT /prices/:productId y publicalo después con PATCH.
  • PATCH /products/:id edita los datos del producto. En las variantes sólo se editan las existentes (por id); las variantes nuevas se crean únicamente al crear el producto.
  • DELETE /products/:id da de baja el producto. Si tiene reservas vigentes responde 409 conflict con details[0].reason = "has_reservations".

Precios (write_prices)

PUT /prices/:productId es el único camino para cambiar precios. Body:
Cambia el precio base del producto y/o de sus variantes (mayores a 0). Con expected hacés una escritura condicional: si el precio actual no es el esperado responde 409 conflict. En productos con variantes no se acepta price a nivel producto: el precio base se deriva de las variantes.

Stock (write_stock)

POST /stock/adjustments (con Idempotency-Key obligatorio):
Usá delta (entero distinto de 0, suma o resta) o set (cantidad absoluta), nunca los dos. variant es obligatorio si el producto tiene variantes. Si el ajuste dejaría el stock en negativo responde 409 conflict con details[0].reason = "insufficient_stock". Devuelve la fila de stock actualizada.

Ventas (write_orders)

  • POST /orders/:id/status con { "status": "...", "notes": "..." } cambia el estado. Los estados permitidos son confirmed, preparing, shipped y delivered, y tienen que existir en el flujo de estados de la tienda. Nunca se puede cancelar, rechazar ni devolver una venta desde la API. Si la entrega exige confirmar el cobro (por ejemplo un retiro pagado en efectivo), responde 409 conflict con details[0].reason = "payment_confirmation_required": esa confirmación se hace desde Commercy.
  • POST /orders/:id/fulfillment con { "tracking_number": "...", "tracking_url": "..." } carga los datos de seguimiento. Si la venta no tiene datos de envío responde 409 conflict con details[0].reason = "no_shipping".
  • POST /orders/:id/payment informa que tu plataforma cobró la venta. Ver Informar un cobro.

Informar un cobro

POST /orders/:id/payment le avisa a Commercy que tu plataforma ya cobró una venta. Necesita el scope write_orders y el mismo permiso del comerciante que POST /orders/:id/status. Responde 200 con la venta, igual que el resto de las escrituras de ventas.
Qué hace Commercy:
  1. Registra el cobro en la venta. En la API pública payment.status pasa a approved.
  2. Emite order.paid con paid_via: "app" (ver Webhooks). Tu app no recibe su propio evento.
  3. Confirma la venta si estaba pending: pasa al primer paso del flujo de estados de la tienda, que no es necesariamente confirmed. Si la venta estaba en otro estado, no se toca.
Registrar el cobro no mueve plata ni cambia el medio de pago de la venta: Commercy sólo deja constancia de que tu app lo cobró. Lo mismo vale al revés: si la venta se cancela, la devolución la hacés vos en tu plataforma (recibís order.cancelled). Este endpoint no sirve para informar reembolsos: la API pública no los recibe.

Respuestas

Motivos del 409 conflict: Para no cobrar dos veces por error, mandá siempre el mismo external_id para el mismo cobro. El header Idempotency-Key también sirve (ver la sección Idempotencia más arriba), pero es opcional y la protección no depende de él. Algunos detalles:
  • Ventas del POS ya cobradas. Una venta de mostrador que no es a cuenta corriente ya emitió order.paid (con paid_via: "pos") al crearse. Sobre esas ventas el endpoint sólo atribuye el cobro a tu app, para conciliar, y no emite un evento nuevo.
  • payment.status en ventas sin pago online. Vale pending en toda venta que no tiene un pago online registrado, aunque se haya cobrado en el local. No lo uses para saber si una venta está cobrada si no fue por un medio online o por tu app.

Clientes (write_customers)

  • POST /customers con name (obligatorio), email, phone, document ({ "type": "DNI", "number": "..." }) y shipping_addresses.
  • PATCH /customers/:id con los mismos campos, todos opcionales. Solo se admite DNI como documento: los datos fiscales no se gestionan por la API.

DELETE /installation

Sin scope. La app se desinstala a sí misma. Responde { "data": { "uninstalled": true } } y el access token deja de valer en el siguiente request.