Skip to Content
Envío por API

Envío por API

Para el equipo que prefiere integrar en vez de subir archivos a mano. Si no es su caso, la página de envío hace lo mismo sin escribir una línea de código.

Esta guía alcanza para el primer envío. El contrato completo —cada endpoint, cada campo y cada respuesta, con ejemplos que se pueden probar— está en la referencia, que se abre en otra pestaña para poder leerla al lado del código:

Un 202 significa que lo guardamos, no que el contenido esté bien. No validamos nada al recibir: revisamos después y le escribimos si algo no cierra.

El token

Le entregamos un token por empresa. No vence.

Authorization: Bearer su-token

Viaja sólo desde su servidor, nunca desde un navegador ni una aplicación móvil. Si se filtra, escríbanos y lo rotamos.

Cada cuánto

CadenciaQué lleva cada envío
VentasUn envío por díaUn solo date: el día cerrado, con una fila por artículo, tienda y banner
StockUn envío por díaUn solo as_of: la foto de ese mismo cierre

Es un ciclo por día, los siete días de la semana: cada día se envían la venta del día anterior ya cerrado y la foto de stock de ese cierre. date y as_of coinciden. El día en curso no se envía.

Mande el stock aunque no haya vendido nada. Los dos envíos van todos los días. Si un día llega el stock y no llegan ventas, sabemos que ese día no vendió; si no llega ninguno de los dos, no sabemos si no vendió o no reportó.

Los días sin ventas no se llama a /v1/sales: un cuerpo vacío devuelve 400.

Por eso los dos envíos de un día van juntos o no va ninguno, y en ese orden: primero las ventas, después el stock. Si el envío de ventas falla, no mande el stock de ese día — preferimos un día sin reportar antes que un día que afirma que no vendió. Y si un envío se atrasa, mándelo igual cuando pueda: reenviar el día completo lo corrige.

Los dos envíos

Ventas y stock van por separado, cada uno con su endpoint. El cuerpo es un array, no un objeto: las filas y nada más.

Los ejemplos de abajo dan por sentadas estas dos variables:

BASE=https://sellout.yupana.ai TOKEN=su-token

Ventas

curl -X POST "$BASE/v1/sales" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d @ventas.json

Stock

curl -X POST "$BASE/v1/stock" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d @stock.json

El cuerpo es un array de filas. Una fila de ventas:

[ { "date": "2026-08-05", "banner": { "name": "Yupana Sport" }, "point_of_sale": { "name": "Yupana Sport Palermo", "type": "physical" }, "product": { "sku": { "model": "FI310882", "variation": "NEGRO", "size": "42", "size_scale": "AR" }, "brand": "Fila" }, "currency": "ARS", "quantity": 2, "total_amount_at_sale_price_excl_vat": 141308.54, "total_amount_at_list_price_excl_vat": 163333.33, "total_amount_at_cost_excl_vat": 84000.00 } ]

Todos los campos están en Qué lleva cada fila y en la referencia enlazada arriba.

Envíos grandes

El límite es 10 MB por envío. Una foto de stock completa suele pasarlo, así que mándelo comprimido:

gzip -c stock.json | curl -X POST "$BASE/v1/stock" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/octet-stream" \ -H "Content-Encoding: gzip" \ --data-binary @-

Un JSON de sell-out comprime cerca de diez veces, así que con gzip entran unas 200.000 filas de ventas.

Conviene declarar el envío comprimido con Content-Type: application/octet-stream, que es lo que es. Con application/json también funciona.

Qué devuelve

{ "upload_id": "9f3a1c7e4b2d48a6", "company": "Yupana Sport", "kind": "sales", "rows": 4212, "bytes": 2098431, "received_at": "2026-08-12T15:04:05+00:00", "key": "yupanasport/sales/2026/08/12/9f3a1c7e4b2d48a6-api.json" }

rows son las filas que contamos. Compárelo con lo que mandó: si no coincide, el problema está de su lado y conviene verlo ahora y no dentro de un mes.

Viene en null cuando el cuerpo no se pudo interpretar o era demasiado grande para contarlo. El envío se guardó igual.

Guarde el upload_id: es lo que hay que citar si nos escribe por ese envío.

Errores

HTTPQué pasó
400El cuerpo vino vacío
401Falta el token o no es válido
413El envío supera los 10 MB. Mándelo con gzip
500No pudimos guardarlo. Ya estamos avisados; reintente

No hay errores de validación de contenido porque no validamos el contenido.

Antes de producción

Le damos un token de prueba contra el ambiente de staging, que es igual a producción pero guarda aparte:

BASE=https://sellout-staging.yupana.ai

Mande unos días reales ahí, los revisamos juntos, y recién entonces le entregamos el token de producción. Lo único que cambia entre un ambiente y el otro son el BASE y el token.

El token de staging no sirve en producción ni al revés. Si se confunde de par, la respuesta es 401.