Pedidos y tu ERP por API
Registrar y recibir órdenes de compra, y sacar las órdenes aprobadas hacia tu ERP con un cursor, en el formato de tu ERP, devolviendo el número de documento.
En esta página
Registrar la orden que salió
POST /inventory/log-po?session_id=... registra una orden basada en las recomendaciones. Sin ella, la orden no existe para StockAI: no se espera su recepción y nunca se aprende el tiempo de entrega real de tus proveedores. Manda lo que decidió el comprador por línea, incluidas las rechazadas:
curl -X POST "https://app.stockai.es/api/v1/inventory/log-po?session_id=$SESSION" \
-H "Authorization: Bearer $STOCKAI_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2a0e-1b7d-4c55-9a51-3f0f4e0b8d21" \
-d '{"items":[{"sku":"SKU-001","recommended_qty":120,"final_qty":100,
"status":"modified","unit_cost":2.5,"supplier":"Distribuidora Andina"}]}'
# First call: 201, "replayed": false -> the order was created
# Same call again (same key, same lines): 200, "replayed": true -> the FIRST order, nothing new
# Same key, different lines: 409 po_idempotency_key_reuseditems[].statusapproved(tal cual),modified(cambió la cantidad) orejected(no se pidió). Es la medida de cuánto se sigue a StockAI.items[].recommended_qty/final_qty- Lo que recomendó StockAI y lo que se pidió.
items[].unit_cost,supplier,supplier_id,warehouse,currency- Opcionales por línea.
destination_warehouse- A qué bodega llega. Si tu llave está limitada a bodegas, debe ser una de las suyas.
cost_center_id- Centro de costo al que se imputa (opcional).
- Sin
items - Se registran todos los SKUs en
PEDIR_YA/PEDIR_PRONTOde la sesión, pero sin decisiones por línea (no cuenta para la adopción).
{
"id": "fc92e89e-b1c7-4eab-807f-97d173565d19",
"po_number": 1,
"session_id": "sess_b70dd9d55a81",
"sku_count": 1, "total_units": 100.0, "total_value": 250.0,
"suggested_count": 1, "approved_count": 1, "modified_count": 1, "rejected_count": 0,
"reception_status": "pending",
"destination_warehouse": null,
"source": "forecast",
"replayed": false
}Una orden escrita a mano
curl -X POST https://app.stockai.es/api/v1/inventory/po \
-H "Authorization: Bearer $STOCKAI_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: 0b9a7d4e-2f61-4f0e-9a7b-6c8f1d2e3a45" \
-d '{"supplier_id":"de7514c0-44b6-41c5-90d6-877801d45fc5",
"lines":[{"sku":"SKU-004","qty":60,"unit_cost":3.1}],
"destination_warehouse":"principal"}'POST /inventory/po crea una orden sin sesión detrás (source: "manual"): supplier_id y lines (sku, qty, unit_cost opcional) obligatorios. Acepta la misma Idempotency-Key. Si el proveedor no existe: 404 supplier_not_found.
Importar órdenes desde un archivo
POST /inventory/po/import/preview y POST /inventory/po/import (multipart file, mapping opcional) crean una orden por cada (referencia, proveedor, bodega) del archivo. Importar el mismo archivo dos veces no duplica. Plantilla: GET /inventory/po/import/template.
El ciclo de vida de una orden
| Método y ruta | Qué hace | Llave |
|---|---|---|
GET /inventory/po-history/page | Historial paginado (limit, offset, status, q). | lectura |
GET /inventory/po/{id}/items | Las líneas de una orden. | lectura |
POST /inventory/po/{id}/send | Manda el PDF a cada proveedor por correo y WhatsApp. Los que no tienen contacto se omiten y se informan. Necesita que un administrador de la cuenta tenga el correo verificado. | escritura |
POST /inventory/po/{id}/receive | Registra la llegada, total o parcial. Suma el stock y aprende el tiempo de entrega real. | escritura |
POST /inventory/po/{id}/unreceive, .../unsend | Deshace una recepción o un envío marcado. | escritura |
GET /inventory/po/overdue | Órdenes cuya llegada esperada ya pasó sin recepción. | lectura |
GET /inventory/status/export-po | La orden sugerida como CSV (session_id, signals, warehouse). | lectura |
# Partial reception: 60 of the 100 units arrived. received_at is optional (default: now)
# and cannot be earlier than the order date (422 reception_date_before_order).
curl -X POST https://app.stockai.es/api/v1/inventory/po/$PO_ID/receive \
-H "Authorization: Bearer $STOCKAI_KEY" -H "Content-Type: application/json" \
-d '{"lines":[{"sku":"SKU-001","received_qty":60}]}'
# "data": { "po_log_id": "fc92e89e-...", "reception_status": "partial",
# "received_at": "2026-10-23T03:00:31.257904+00:00", "lead_time_days": 0.0,
# "items": [{ "sku": "SKU-001", "final_qty": 100.0, "received_qty": 60.0, ... }] }El feed de órdenes aprobadas para tu ERP
GET /purchase-orders/export devuelve las órdenes aprobadas que cambiaron desde la última vez que leíste, la más antigua primero, con un cursor para continuar. Es una lectura pura: funciona con una llave de lectura y no marca nada.
curl "https://app.stockai.es/api/v1/purchase-orders/export?limit=100" \
-H "Authorization: Bearer $STOCKAI_KEY"{
"orders": [{
"id": "fc92e89e-b1c7-4eab-807f-97d173565d19",
"number": "OC-000001",
"state": "open", // open | partially_received | received | cancelled
"cancelled_at": null, "cancel_reason": null,
"created_at": "2026-10-22T20:59:16.750335-06:00",
"documents": [{ // one per supplier
"document_id": "fc92e89e-b1c7-4eab-807f-97d173565d19:-", // stable on every export
"number": "OC-000001",
"supplier": { "id": null, "name": "Distribuidora Andina",
"erp_code": null, "mapped": false }, // no erp_code yet: flagged
"currency": null, "mixed_currency": false,
"order_date": "2026-10-22",
"expected_date": "2026-11-06", "expected_date_source": "model",
"warehouse": "principal",
"erp_reference": null, "acknowledged": false,
"lines": [{ "line_id": "d3afc717-a782-4298-9375-b7033d9722be", "sku": "SKU-001",
"name": null, "quantity": 100.0, "received_quantity": 0.0,
"unit_cost": 2.5, "currency": null, "total": 250.0 }]
}]
}],
"next_cursor": "po_10",
"has_more": false,
"held_back": 0
}since- El
next_cursorde la página anterior (tiene la formapo_<número>). Sin él empiezas desde el principio. Un cursor que no emitió este feed se rechaza con400 feed_cursor_invalid. limit- Órdenes por página: 100 por defecto, máximo 500.
has_moretruesi hay otra página: pídela enseguida con el nuevo cursor.held_back- Órdenes que cambiaron pero todavía esperan aprobación. No salen hasta aprobarse; cuando se aprueban, vuelven a aparecer solas.
stateopen,partially_received,receivedocancelled. Una orden cancelada sigue en el feed con ese estado: tu ERP debe anular la suya.documents[]- Un documento por proveedor.
document_idyline_idson los mismos en cada exportación: úsalos para no crear la compra dos veces. supplier.mappedfalsesi el proveedor no tieneerp_code: el documento sale igual, marcado, para que no se pierda.expected_date_source- De dónde sale la fecha esperada: el tiempo de entrega aprendido, o una fecha que el proveedor prometió y una persona aceptó.
CURSOR=$(cat .stockai_cursor 2>/dev/null || true)
while :; do
PAGE=$(curl -sf "$API/purchase-orders/export?limit=100${CURSOR:+&since=$CURSOR}" -H "$AUTH")
echo "$PAGE" | jq -c '.data.orders[]' | while read -r ORDER; do
import_into_erp "$ORDER" # your code; match on documents[].document_id
done
CURSOR=$(echo "$PAGE" | jq -r '.data.next_cursor // empty')
[ -n "$CURSOR" ] && echo "$CURSOR" > .stockai_cursor # save AFTER importing
[ "$(echo "$PAGE" | jq -r '.data.has_more')" = "true" ] || break
done- Lee una página con el cursor que guardaste (ninguno la primera vez).
- Importa cada orden en tu ERP, emparejando por
document_id. - Guarda
next_cursordespués de importar, no antes: si tu proceso se cae a la mitad, vuelve a leer lo que no terminó. - Si
has_moreestrue, repite; si no, espera hasta la próxima corrida. - Escribe de vuelta el número que le dio tu ERP a cada documento (abajo).
El archivo en el formato de tu ERP
GET /purchase-orders/export/file?po_ids=<id1>,<id2>&format=<clave> devuelve las órdenes elegidas como el archivo que importa tu ERP, un documento por proveedor. Las canceladas y las que esperan aprobación se rechazan (409 export_order_cancelled, 409 po_approval_required). GET /purchase-orders/export/formats lista los formatos:
format | Formato | Extensión | Probado contra un sistema real |
|---|---|---|---|
csv | CSV genérico | .csv | Sí |
xlsx | Excel genérico | .xlsx | Sí |
sap | SAP (estructura BAPI_PO_CREATE1) | .txt | No — faltan COMP_CODE, PURCH_ORG, PUR_GROUP, PLANT |
dynamics_bc | Dynamics 365 / Business Central | .csv | No |
netsuite | Importación CSV de NetSuite | .csv | No |
odoo | Odoo | .csv | No |
oracle_ebs | Oracle E-Business Suite (open interface) | .csv | No — falta SHIP_TO_ORGANIZATION_CODE |
curl "https://app.stockai.es/api/v1/purchase-orders/export/file?po_ids=$PO_ID&format=sap" \
-H "Authorization: Bearer $STOCKAI_KEY" -o purchase_orders_sap.txtDevolver el número de documento del ERP
curl -X POST https://app.stockai.es/api/v1/purchase-orders/$PO_ID/erp-reference \
-H "Authorization: Bearer $STOCKAI_KEY" -H "Content-Type: application/json" \
-d '{"reference":"4500012345"}'
# Several suppliers on the order? Say which document: {"reference":"...","supplier_id":"de7514c0-..."}
# "data": { "changed": true, "po_log_id": "fc92e89e-...", "reference": "4500012345",
# "document_id": "fc92e89e-...:-", "number": "OC-000001" }
# The same call again: "changed": false. A different number for the same document: 409.- Guarda el número que tu ERP le dio al documento. Es idempotente: repetirlo con el mismo número responde
changed: false. - Si la orden tiene documentos de varios proveedores, manda
supplier_id; si no,400 erp_reference_supplier_requiredte lista los proveedores. - Un número distinto para un documento que ya tiene uno:
409 erp_reference_conflict. Un número que ya usa otra orden:409 erp_reference_in_use. Nunca se sobreescribe. - Cualquier cambio en una orden la vuelve a poner en el feed, también escribir su referencia: la verás otra vez con
acknowledged: trueyerp_referencelleno. Tu importador debe saltarse los documentos que ya tiene (pordocument_id). GET /purchase-orders/unacknowledgedlista las órdenes abiertas y aprobadas con documentos que tu ERP todavía no confirmó: tu tarea pendiente.