API v1.1.0 · REST y JSON
API de Nori
Es la misma API que usa la app móvil. Lees catálogos, creas pedidos y facturas, registras cobros y trabajas sin señal con las reglas y los permisos que ya tiene la web: lo que un usuario no puede hacer en pantalla tampoco lo puede hacer por API.
URL base Cada empresa tiene su dominio y su base de datos.
https://{empresa}.nori.software/api/v1
- Formato
- JSON; importes como cadenas decimales
- Autenticación
- Bearer, un token por dispositivo
- Escrituras
- Idempotency-Key obligatoria
- Paginación
- Por cursor, hasta 200 por página
- Límite
- 240 peticiones por minuto
¿Vas a integrar un sistema? Escríbenos y lo revisamos contigo.
curl -X POST https://demo.nori.software/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "ana",
"password": "secreto",
"device_name": "Pixel 8 de Ana",
"tenant": "demo"
}'
{
"data": {
"token": "12|Xk2m...",
"token_type": "Bearer",
"expires_at": "2026-11-03T10:15:00-06:00",
"tenant": {
"id": "demo",
"name": "Demo",
"domain": "demo.nori.software"
},
"signed_out_devices": [
"Pixel 8 de Ana"
],
"user": {
"id": 42,
"username": "ana",
"name": "Ana López",
"email": "ana@example.com",
"mobile_phone": "+528112345678",
"locale": "es_MX",
"is_admin": false,
"roles": [
"Vendedor"
],
"role_labels": [
"Sales rep"
]
},
"license_type": "cajero",
"permissions": [
"quotations.create",
"sales_orders.create",
"incoming_payments.create"
],
"company": {
"name": "Demo, S.A. de C.V.",
"accent_color": "#2563eb",
"country": "MX",
"logo": "…"
},
"working_context": {
"branch": {},
"sales_employee": {},
"default_partner": {},
"warehouse_id": 42,
"must_choose_warehouse": false,
"warehouses": [
{}
],
"allowed_price_list_ids": [
42
],
"minimum_price_list_id": 42,
"max_discount_percent": 1.5,
"allowed_warehouse_ids": [
42
],
"tender_types": [
{}
]
}
}
}
Guía
Autenticación
Cada dispositivo inicia sesión con el usuario y la contraseña de la web y recibe su propio token. Volver a iniciar sesión en el mismo dispositivo (device_name) reemplaza el token anterior.
Manda el token en cada llamada con Authorization: Bearer <token>. Vence a los 30 días: refréscalo con POST /auth/refresh cuando falten pocos días para expires_at, y el anterior deja de servir en ese momento.
El login acepta cinco intentos por minuto por usuario e IP. Con GET /auth/devices y DELETE /auth/devices/{token} el usuario cierra la sesión de un teléfono perdido.
curl https://demo.nori.software/api/v1/me \
-H "Authorization: Bearer $NORI_TOKEN"
curl -X POST https://demo.nori.software/api/v1/auth/refresh \
-H "Authorization: Bearer $NORI_TOKEN"
Guía
Empresa
Cada empresa tiene su dominio y su propia base de datos. Lo más simple es llamar al dominio de la empresa, igual que la web: https://tu-empresa.nori.software/api/v1.
Desde el dominio central (api.nori.software) manda el código de la empresa en el encabezado X-Tenant; en el login también sirve el campo tenant. El login responde tenant.domain, así que basta con pedir el código una vez.
Un token solo existe en la base de su empresa y no sirve en otra.
| HTTP | code | Descripción |
|---|---|---|
| 400 | tenant_required | Falta X-Tenant en el dominio central
|
| 404 | tenant_not_found | El código de empresa no existe |
| 403 | account_suspended | La empresa está suspendida |
# Dominio de la empresa
curl https://tu-empresa.nori.software/api/v1/items \
-H "Authorization: Bearer $NORI_TOKEN"
# Dominio central
curl https://api.nori.software/api/v1/items \
-H "X-Tenant: tu-empresa" \
-H "Authorization: Bearer $NORI_TOKEN"
Guía
Formato y paginación
Las respuestas exitosas vienen en {"data": …} y algunas agregan meta. Los endpoints de venta en ruta (/route/*) responden sin data, con la misma forma que usa la pantalla web de la ruta.
Importes y cantidades son cadenas decimales con la precisión guardada ("232.000000"). No los conviertas a double para hacer cuentas de dinero. Las fechas van en YYYY-MM-DD y los instantes en ISO 8601 con zona horaria.
Las listas se paginan por cursor. Pide ?per_page=50 (hasta 200) y sigue con cursor=meta.next_cursor hasta que llegue null.
Documentos y pagos traen pdf_url, una liga firmada que sirve 30 minutos para abrir o compartir el PDF sin token.
{
"data": [
{
"id": 5,
"code": "ROSA",
"name": "Rosa roja",
"barcode": "7501055363057",
"uom": "Pieza",
"is_active": true
}
],
"links": {
"next": "https://demo.nori.software/api/v1/items?cursor=eyJpZCI6NX0",
"prev": null
},
"meta": {
"next_cursor": "eyJpZCI6NX0",
"prev_cursor": null,
"per_page": 50
}
}
Guía
Búsqueda
Clientes y artículos aceptan search. En clientes busca por código, nombre, nombre comercial, RFC o teléfono; en artículos, por código o descripción. Para un lector de código de barras usa barcode, que busca el código exacto.
Con mine=true un vendedor ve solo a sus clientes. Para un checador de precios, GET /items/{item}/price-check regresa precios por lista y unidad con impuesto, la existencia por almacén y la ubicación habitual.
curl "https://demo.nori.software/api/v1/customers?search=don%20pepe&mine=true" \
-H "Authorization: Bearer $NORI_TOKEN"
curl "https://demo.nori.software/api/v1/items?barcode=7501055363057" \
-H "Authorization: Bearer $NORI_TOKEN"
Guía
Errores
Todos los errores usan el mismo sobre: un message listo para mostrar y un objeto error con un code estable, el field que hay que corregir cuando aplica y un request_id para soporte.
authorization_required trae la solicitud firmada que también recibe la pantalla web. No la modifiques: úsala con los endpoints de autorizaciones para pedir la aprobación o aplicar una ya dada.
| HTTP | code | Descripción |
|---|---|---|
| 422 | validation | Datos inválidos; fields trae los mensajes por campo
|
| 422 | business_rule | Una regla del negocio (crédito, existencia…); field dice qué corregir
|
| 422 | authorization_required | Necesita un supervisor; authorization trae la solicitud
|
| 401 | unauthenticated | Sin token, o con un token vencido o revocado |
| 403 | forbidden | El usuario no tiene el permiso o está fuera de su alcance |
| 404 | not_found | El recurso no existe |
| 428 | idempotency_key_required | Escritura sin Idempotency-Key
|
| 422 | idempotency_key_reused | La llave ya se usó con otra petición |
| 429 | rate_limited | Demasiadas peticiones; espera lo que indique Retry-After
|
| 500 | server_error | Error inesperado; muestra el request_id al usuario
|
{
"message": "El pago excede el saldo de Factura A-120 (232.00).",
"error": {
"code": "business_rule",
"message": "El pago excede el saldo de Factura A-120 (232.00).",
"field": "applications",
"request_id": "K3J9QX2A"
}
}
Guía
Idempotencia
Toda escritura (POST, PUT, PATCH y DELETE) lleva Idempotency-Key con un UUID v4. Genera la llave cuando el usuario actúa, guárdala junto con la operación y usa la misma en cada reintento.
- 01La primera vez se ejecuta y la respuesta exitosa queda guardada.
- 02Con la misma llave y la misma petición regresa la respuesta guardada con
Idempotent-Replayed: true, sin volver a ejecutar nada. - 03La misma llave con otra petición responde
422 idempotency_key_reused. - 04Si la operación falló no se guarda, así que puedes corregir y reintentar con la misma llave.
No llevan llave el login, refresh, logout, revocar un dispositivo, los cálculos de /pricing/* y /sync/push, donde cada operación trae la suya. En venta en ruta la llave es opcional.
curl -X POST https://demo.nori.software/api/v1/customers \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"name": "Abarrotes Don Pepe",
"phone": "3312345678",
"latitude": 20.67,
"longitude": -103.35
}'
HTTP/1.1 201 Created
Idempotent-Replayed: true
Content-Type: application/json
Guía
Límites
Al pasar el límite la API responde 429 rate_limited con el encabezado Retry-After en segundos.
| Peticiones por usuario y empresa | 240 por minuto |
| Inicio de sesión | 5 por minuto por usuario e IP |
| Registros por página de una lista | Hasta 200 |
| Filas por página de sincronización | 500 por omisión, hasta 2,000 |
| Operaciones por envío de la cola | 100 |
HTTP/1.1 429 Too Many Requests
Retry-After: 12
{
"message": "Demasiadas peticiones.",
"error": {
"code": "rate_limited",
"message": "Demasiadas peticiones.",
"request_id": "K3J9QX2A"
}
}
Guía
Trabajo sin conexión
GET /sync lista los catálogos que el usuario puede guardar en el teléfono según sus permisos: artículos, precios, existencias, clientes, listas de precios, formas de cobro, almacenes, causas y motivos de visita.
Cada catálogo se baja por cambios. Sin token, o con uno muy viejo, llega meta.reset: true y hay que vaciar la tabla local. Borra lo que llegue en deleted, inserta o actualiza data por id y guarda meta.sync_token solo al terminar todas las páginas.
Lo que se captura sin señal se manda en orden con POST /sync/push, hasta 100 operaciones por envío. Una que falla no detiene a las demás. Un valor "$ref:<key>:<ruta>" toma un dato de una operación anterior del mismo envío, como el id de un cliente creado sin señal.
token = guardado[entidad] # null la primera vez
cursor = null
loop:
r = GET /sync/{entidad}?updated_since={token}&cursor={cursor}
if r.meta.reset and cursor == null: vaciar la tabla local
borrar r.deleted # primero borrar
upsert r.data por id # después insertar o actualizar
if not r.meta.has_more: break
cursor = r.meta.next_cursor
guardado[entidad] = r.meta.sync_token
curl -X POST https://demo.nori.software/api/v1/sync/push \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"operations": [
{
"key": "6f1c2a9e-0d0b-4b8e-9a43-0c1f1f3c0001",
"method": "POST",
"path": "/api/v1/customers",
"body": {
"name": "Tienda nueva"
}
},
{
"key": "6f1c2a9e-0d0b-4b8e-9a43-0c1f1f3c0002",
"method": "POST",
"path": "/api/v1/documents",
"body": {
"type": "sales_order",
"business_partner_id": "$ref:6f1c2a9e-0d0b-4b8e-9a43-0c1f1f3c0001:data.id",
"lines": [
{
"item_id": 5,
"quantity": 1
}
]
}
}
]
}'
Guía
Versionado
El prefijo /api/v1 es la versión mayor. Dentro de v1 solo entran cambios compatibles: endpoints, campos o valores nuevos, así que ignora los campos que no conozcas.
Un cambio incompatible abre /api/v2. La versión anterior se mantiene al menos seis meses y lo avisa con los encabezados Deprecation, Sunset y Link.
Deprecation: true
Sunset: Wed, 30 Jun 2027 23:59:59 GMT
Link: <https://demo.nori.software/api/v2>; rel="successor-version"
Referencia
Autenticación
Inicia sesión desde un dispositivo, renueva o revoca su token y administra los dispositivos con sesión abierta.
Iniciar sesión en un dispositivo
POST
/auth/login
Sin sesión
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| X-Tenant | string | no | Código de la empresa, solo en un dominio central. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| username | string | sí | |
| password | string | sí | |
| device_name | string | sí | Un token por dispositivo: iniciar sesión de nuevo lo reemplaza. |
| tenant | string | no | Código de la empresa, solo necesario en un dominio central. |
Respuestas
- 201 Token y sesión
- 401 Sobre de error
- 422 Sobre de error
- 429 Sobre de error
curl -X POST https://demo.nori.software/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "ana",
"password": "secreto",
"device_name": "Pixel 8 de Ana",
"tenant": "demo"
}'
{
"data": {
"token": "12|Xk2m...",
"token_type": "Bearer",
"expires_at": "2026-11-03T10:15:00-06:00",
"tenant": {
"id": "demo",
"name": "Demo",
"domain": "demo.nori.software"
},
"signed_out_devices": [
"Pixel 8 de Ana"
],
"user": {
"id": 42,
"username": "ana",
"name": "Ana López",
"email": "ana@example.com",
"mobile_phone": "+528112345678",
"locale": "es_MX",
"is_admin": false,
"roles": [
"Vendedor"
],
"role_labels": [
"Sales rep"
]
},
"license_type": "cajero",
"permissions": [
"quotations.create",
"sales_orders.create",
"incoming_payments.create"
],
"company": {
"name": "Demo, S.A. de C.V.",
"accent_color": "#2563eb",
"country": "MX",
"logo": "…"
},
"working_context": {
"branch": {},
"sales_employee": {},
"default_partner": {},
"warehouse_id": 42,
"must_choose_warehouse": false,
"warehouses": [
{}
],
"allowed_price_list_ids": [
42
],
"minimum_price_list_id": 42,
"max_discount_percent": 1.5,
"allowed_warehouse_ids": [
42
],
"tender_types": [
{}
]
}
}
}
Renovar el token del dispositivo
POST
/auth/refresh
Respuestas
- 200 Nuevo token y sesión
- 401 Sobre de error
curl -X POST https://demo.nori.software/api/v1/auth/refresh \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"token": "12|Xk2m...",
"token_type": "Bearer",
"expires_at": "2026-11-03T10:15:00-06:00",
"tenant": {
"id": "demo",
"name": "Demo",
"domain": "demo.nori.software"
},
"signed_out_devices": [
"Pixel 8 de Ana"
],
"user": {
"id": 42,
"username": "ana",
"name": "Ana López",
"email": "ana@example.com",
"mobile_phone": "+528112345678",
"locale": "es_MX",
"is_admin": false,
"roles": [
"Vendedor"
],
"role_labels": [
"Sales rep"
]
},
"license_type": "cajero",
"permissions": [
"quotations.create",
"sales_orders.create",
"incoming_payments.create"
],
"company": {
"name": "Demo, S.A. de C.V.",
"accent_color": "#2563eb",
"country": "MX",
"logo": "…"
},
"working_context": {
"branch": {},
"sales_employee": {},
"default_partner": {},
"warehouse_id": 42,
"must_choose_warehouse": false,
"warehouses": [
{}
],
"allowed_price_list_ids": [
42
],
"minimum_price_list_id": 42,
"max_discount_percent": 1.5,
"allowed_warehouse_ids": [
42
],
"tender_types": [
{}
]
}
}
}
Revocar el token del dispositivo
POST
/auth/logout
Respuestas
- 200 Hecho
- 401 Sobre de error
curl -X POST https://demo.nori.software/api/v1/auth/logout \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"message": "Listo."
}
}
Dispositivos con sesión abierta
GET
/auth/devices
Respuestas
- 200 Dispositivos
curl https://demo.nori.software/api/v1/auth/devices \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"id": 42,
"device_name": "Pixel 8 de Ana",
"last_used_at": "2026-11-03T10:15:00-06:00",
"expires_at": "2026-11-03T10:15:00-06:00",
"created_at": "2026-11-03T10:15:00-06:00",
"is_current": false
}
]
}
Cerrar la sesión de un dispositivo
DELETE
/auth/devices/{token}
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| token | integer | sí |
Respuestas
- 200 Hecho
- 404 Sobre de error
curl -X DELETE https://demo.nori.software/api/v1/auth/devices/18 \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"message": "Listo."
}
}
Usuario, permisos y contexto de trabajo
GET
/me
Respuestas
- 200 Sesión
curl https://demo.nori.software/api/v1/me \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"user": {
"id": 42,
"username": "ana",
"name": "Ana López",
"email": "ana@example.com",
"mobile_phone": "+528112345678",
"locale": "es_MX",
"is_admin": false,
"roles": [
"Vendedor"
],
"role_labels": [
"Sales rep"
]
},
"license_type": "cajero",
"permissions": [
"quotations.create",
"sales_orders.create",
"incoming_payments.create"
],
"company": {
"name": "Demo, S.A. de C.V.",
"accent_color": "#2563eb",
"country": "MX",
"logo": "…"
},
"working_context": {
"branch": {},
"sales_employee": {},
"default_partner": {},
"warehouse_id": 42,
"must_choose_warehouse": false,
"warehouses": [
{}
],
"allowed_price_list_ids": [
42
],
"minimum_price_list_id": 42,
"max_discount_percent": 1.5,
"allowed_warehouse_ids": [
42
],
"tender_types": [
{}
]
}
}
}
Elegir el almacén de trabajo
PUT
/me/working-warehouse
Idempotency-Key
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| warehouse_id | integer | sí |
Respuestas
- 200 Sesión con el nuevo almacén de trabajo
- 422 Sobre de error
curl -X PUT https://demo.nori.software/api/v1/me/working-warehouse \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"warehouse_id": 3
}'
{
"data": {
"user": {
"id": 42,
"username": "ana",
"name": "Ana López",
"email": "ana@example.com",
"mobile_phone": "+528112345678",
"locale": "es_MX",
"is_admin": false,
"roles": [
"Vendedor"
],
"role_labels": [
"Sales rep"
]
},
"license_type": "cajero",
"permissions": [
"quotations.create",
"sales_orders.create",
"incoming_payments.create"
],
"company": {
"name": "Demo, S.A. de C.V.",
"accent_color": "#2563eb",
"country": "MX",
"logo": "…"
},
"working_context": {
"branch": {},
"sales_employee": {},
"default_partner": {},
"warehouse_id": 42,
"must_choose_warehouse": false,
"warehouses": [
{}
],
"allowed_price_list_ids": [
42
],
"minimum_price_list_id": 42,
"max_discount_percent": 1.5,
"allowed_warehouse_ids": [
42
],
"tender_types": [
{}
]
}
}
}
Pedir que se elimine la cuenta del usuario
POST
/me/deletion-request
Idempotency-Key
El usuario con sesión pide que Nori borre su cuenta y sus datos personales (nombre, correo, teléfono, accesos de dispositivos, sesiones y los puntos GPS de sus visitas de ruta) en un máximo de 30 días. Los documentos y registros fiscales de la empresa se conservan y el usuario aparece en ellos como «Usuario eliminado #id». La sesión verifica la solicitud; se avisa a Nori y a los administradores de la empresa. Si el usuario ya tiene una solicitud abierta, se devuelve esa con 200. Después, la app debe cerrar sesión y borrar sus datos locales.
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| reason | string | null | no | Motivo, opcional |
Respuestas
- 201 Solicitud recibida
- 200 La solicitud abierta del usuario, ya recibida
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/me/deletion-request \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"reason": "Ya no trabajo en la empresa"
}'
{
"data": {
"id": 12,
"status": "verified",
"created_at": "2026-10-23T18:00:00-06:00",
"message": "Recibimos tu solicitud. Borraremos tu usuario y tus datos personales en un máximo de 30 días y te avisaremos por correo."
}
}
Referencia
Meta
Consulta la sesión del usuario, sus permisos, el almacén de trabajo, la hora del servidor y los catálogos de referencia.
Hora del servidor y versión de la API
GET
/meta/server-time
Sin sesión
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| X-Tenant | string | no | Código de la empresa, solo en un dominio central. |
Respuestas
- 200 Hora del servidor
curl https://demo.nori.software/api/v1/meta/server-time
{
"data": {
"server_time": "2026-11-03T10:15:00-06:00",
"timezone": "America/Mexico_City",
"api_version": "1.0.0",
"tenant": "demo"
}
}
Catálogos de referencia
GET
/catalogs
Respuestas
- 200 Catálogos (siempre completos)
curl https://demo.nori.software/api/v1/catalogs \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"currencies": [
{
"id": 1,
"code": "MXN",
"name": "Peso",
"symbol": "$",
"decimals": 2,
"is_local": true
}
],
"units_of_measure": [
{
"id": 1,
"code": "PZA",
"name": "Pieza"
}
]
}
}
Active user-defined fields per entity (documents, document_lines, business_partners, items, payments)
GET
/user-fields
The company's own fields (SAP B1 UDF style,). Each record of those entities carries an udf object with the values by field code; send the same object to write them (documents, their lines and payments). Fields with editable: false come from the ERP and are ignored when sent.
Respuestas
- 200 Definitions by entity
curl https://demo.nori.software/api/v1/user-fields \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"documents": [
{
"code": "U_RUTA",
"label": "Ruta",
"type": "list",
"required": false,
"default": null,
"editable": true,
"values": [
{
"value": "N",
"label": "Norte"
}
]
}
],
"document_lines": [],
"business_partners": [],
"items": [],
"payments": []
}
}
Referencia
Clientes
Busca y da de alta clientes, consulta su cuenta (saldo, crédito, antigüedad) y los documentos abiertos por cobrar.
Buscar clientes
GET
/customers
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| search | string | no | Código, nombre, nombre comercial, RFC o teléfono |
| mine | boolean | no | Solo los clientes del empleado de ventas del usuario |
| route_id | integer | no | |
| include_inactive | boolean | no | |
| per_page | integer | no | Por omisión: 50 Máximo: 200 |
| cursor | string | no | meta.next_cursor de la página anterior |
Respuestas
- 200 Clientes
- 403 Sobre de error
curl https://demo.nori.software/api/v1/customers?search=manzana&per_page=50 \
-H "Authorization: Bearer $NORI_TOKEN"
{
"links": {},
"meta": {
"path": "…",
"per_page": 1,
"next_cursor": "…",
"prev_cursor": "…"
},
"data": [
{
"id": 12,
"code": "C000012",
"type": "customer",
"name": "FLORERÍA LUPITA",
"credit_limit": "1000.000000",
"balance": "0.000000",
"is_active": true
}
]
}
Alta rápida de cliente
POST
/customers
Idempotency-Key
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| name | string | sí | |
| trade_name | string | no | |
| phone | string | no | |
| mobile | string | no | |
| string<email> | no | ||
| rfc | string | no | |
| notes | string | no | |
| latitude | number | no | |
| longitude | number | no |
Respuestas
- 201 Creado
- 403 Sobre de error
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/customers \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"name": "Abarrotes Don Pepe",
"phone": "3312345678",
"latitude": 20.67,
"longitude": -103.35
}'
{
"data": {
"id": 12,
"code": "C000012",
"type": "customer",
"name": "FLORERÍA LUPITA",
"credit_limit": "1000.000000",
"balance": "0.000000",
"is_active": true
}
}
Cliente con direcciones, contactos y saldo
GET
/customers/{customer}
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| customer | integer | sí |
Respuestas
- 200 Cliente
- 404 Sobre de error
curl https://demo.nori.software/api/v1/customers/42 \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"id": 42,
"code": "ROSA",
"type": "…",
"name": "Rosa roja",
"udf": {
"U_RUTA": "N",
"U_PESO": 12.5,
"U_REVISADO": true
},
"trade_name": "…",
"rfc": "XAXX010101000",
"email": "ana@example.com",
"phone": "3312345678",
"mobile": "3312345678",
"partner_group_id": 42,
"price_list_id": 42,
"currency_id": 42,
"payment_terms_id": 42,
"sales_employee_id": 42,
"delivery_route_id": 42,
"discount_percent": "232.000000",
"credit_limit": "232.000000",
"balance": "232.000000",
"cfdi_use": "…",
"tax_regime": "…",
"billing_address_id": 42,
"shipping_address_id": 42,
"contact_id": 42,
"latitude": 1.5,
"longitude": 1.5,
"image_url": "https://demo.nori.software/…",
"is_active": true,
"addresses": [
{}
],
"contacts": [
{}
],
"updated_at": "2026-11-03T10:15:00-06:00",
"account": {
"balance": "232.00",
"credit_limit": "1000.00",
"available_credit": "768.00",
"over_limit": false,
"aging": {
"0_30": "232.00",
"31_60": "0.00",
"61_90": "0.00",
"90_plus": "0.00"
}
},
"defaults": {}
}
}
Documentos abiertos por cobrar
GET
/customers/{customer}/open-documents
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| customer | integer | sí |
Respuestas
- 200 Documentos abiertos
curl https://demo.nori.software/api/v1/customers/42/open-documents \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"id": 42,
"type": "quotation",
"folio": "A-1043",
"document_date": "2026-11-03",
"due_date": "2026-11-03",
"days_overdue": 1,
"bucket": "…",
"currency": "MXN",
"total": "232.000000",
"balance": "232.000000",
"balance_local": "…"
}
],
"meta": {
"balance": "232.000000",
"aging": {
"030": "…",
"3160": "…",
"6190": "…",
"90_plus": "…"
}
}
}
Referencia
Artículos
Busca artículos por texto o código de barras y consulta existencias, lotes, series, ubicaciones, imágenes y precios.
Buscar artículos por texto o código de barras
GET
/items
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| search | string | no | |
| barcode | string | no | Código de barras o código exacto |
| item_group_id | integer | no | |
| sales | boolean | no | |
| inventory | boolean | no | |
| per_page | integer | no | Por omisión: 50 Máximo: 200 |
| cursor | string | no | meta.next_cursor de la página anterior |
Respuestas
- 200 Artículos
curl https://demo.nori.software/api/v1/items?search=manzana&barcode=7501055363057&per_page=50 \
-H "Authorization: Bearer $NORI_TOKEN"
{
"links": {},
"meta": {
"path": "…",
"per_page": 1,
"next_cursor": "…",
"prev_cursor": "…"
},
"data": [
{
"id": 5,
"code": "ROSA",
"name": "Rosa roja",
"is_inventory_item": true,
"is_sales_item": true,
"is_active": true,
"barcodes": [
{
"barcode": "7500000000017",
"uom_id": null,
"is_default": true
}
]
}
]
}
Artículos más vendidos por el usuario
GET
/items/frequent
Pedidos, entregas y facturas que creó el usuario o que llevan a su empleado de ventas; los más frecuentes primero.
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| days | integer | no | Por omisión: 30 Máximo: 365 |
| limit | integer | no | Por omisión: 30 Máximo: 100 |
Respuestas
- 200 Artículos con la cantidad vendida y en cuántos documentos
- 403 Sobre de error
curl https://demo.nori.software/api/v1/items/frequent \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"item_id": 5,
"code": "ROSA",
"name": "Rosa roja",
"uom": "Pieza",
"quantity": "24",
"times": 6
}
]
}
Artículo con imágenes, existencias y kit
GET
/items/{item}
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| item | integer | sí |
Respuestas
- 200 Artículo
- 404 Sobre de error
curl https://demo.nori.software/api/v1/items/42 \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"id": 42,
"code": "ROSA",
"name": "Rosa roja",
"udf": {
"U_RUTA": "N",
"U_PESO": 12.5,
"U_REVISADO": true
},
"description": "…",
"alternate_code": "…",
"item_group_id": 42,
"manufacturer_id": 42,
"is_inventory_item": false,
"is_sales_item": false,
"is_purchase_item": false,
"is_weighted": false,
"is_active": true,
"inventory_uom_id": 42,
"sales_uom_id": 42,
"uom_group_id": 42,
"sales_quantity_per_unit": "232.000000",
"is_taxable": false,
"tax_code_id": 42,
"sales_tax_code_id": 42,
"default_discount_percent": "232.000000",
"image_url": "https://demo.nori.software/…",
"barcodes": [
{
"barcode": "7501055363057",
"uom_id": 42,
"is_default": false
}
],
"is_package": false,
"tracking": "none",
"updated_at": "2026-11-03T10:15:00-06:00",
"images": [
{
"id": 42,
"url": "https://demo.nori.software/…",
"is_main": false,
"width": 1,
"height": 1
}
],
"stock": [
{
"id": 42,
"item_id": 42,
"warehouse_id": 42,
"on_hand": "232.000000",
"committed": "232.000000",
"ordered": "232.000000",
"available": "232.000000",
"is_active": true,
"updated_at": "2026-11-03T10:15:00-06:00"
}
],
"package": {
"item_id": 42,
"name": "Rosa roja",
"code": "ROSA",
"issues_components": false,
"components": [
{
"item_id": null,
"name": null,
"code": null,
"quantity": null
}
],
"groups": [
{
"id": null,
"name": null,
"min": null,
"max": null,
"options": null
}
]
}
}
}
Existencias por almacén
GET
/items/{item}/stock
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| item | integer | sí |
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| warehouse_ids[] | array<integer> | no |
Respuestas
- 200 Existencias
curl https://demo.nori.software/api/v1/items/42/stock \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"id": 42,
"item_id": 42,
"warehouse_id": 42,
"on_hand": "232.000000",
"committed": "232.000000",
"ordered": "232.000000",
"available": "232.000000",
"is_active": true,
"updated_at": "2026-11-03T10:15:00-06:00"
}
]
}
Precio del artículo para un cliente o lista
GET
/items/{item}/price
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| item | integer | sí |
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| partner_id | integer | no | |
| price_list_id | integer | no | |
| quantity | number | no | Por omisión: 1 |
| uom_id | integer | no | |
| warehouse_id | integer | no | |
| document_type | string | no |
Valores: quotationsales_orderdeliveryreturndown_paymentinvoicecredit_memodebit_memotransfer_requestinventory_transfergoods_receiptgoods_issue
|
Respuestas
- 200 Partida cotizada
curl https://demo.nori.software/api/v1/items/42/price \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"item_id": 42,
"item_code": "ROSA",
"description": "…",
"quantity": "232.000000",
"uom_id": 42,
"warehouse_id": 42,
"price": "232.000000",
"discount_percent": "…",
"discount_source": "…",
"promotion_id": 42,
"tax_percent": "…",
"unit_price": "…",
"subtotal": "232.000000",
"tax_total": "…",
"total": "232.000000"
}
}
Galería del artículo
GET
/items/{item}/images
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| item | integer | sí |
Respuestas
- 200 Imágenes
curl https://demo.nori.software/api/v1/items/42/images \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"id": 42,
"url": "https://demo.nori.software/…",
"is_main": false,
"width": 1,
"height": 1
}
]
}
Componentes del kit y opciones de paquete
GET
/items/{item}/package
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| item | integer | sí |
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| warehouse_id | integer | no |
Respuestas
- 200 Paquete
curl https://demo.nori.software/api/v1/items/42/package \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"item_id": 42,
"name": "Rosa roja",
"code": "ROSA",
"issues_components": false,
"components": [
{
"item_id": 42,
"name": "Rosa roja",
"code": "ROSA",
"quantity": 1.5
}
],
"groups": [
{
"id": 42,
"name": "Rosa roja",
"min": 1,
"max": 1,
"options": [
null
]
}
]
}
}
Verificador de precios
GET
/items/{item}/price-check
Precios de las listas que el usuario puede usar (todas para administradores), por unidad (uom_id null = unidad de venta), con price_with_tax calculado con el impuesto de venta del artículo. Existencias de los almacenes que el usuario puede ver, primero el almacén de trabajo; si el almacén usa ubicaciones, bin_location es la ubicación habitual del artículo ahí y bins su existencia por ubicación. Los artículos manejados por lotes listan sus batches con existencia.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| item | integer | sí |
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| warehouse_ids[] | array<integer> | no |
Respuestas
- 200 Artículo para el verificador de precios
- 404 Sobre de error
curl https://demo.nori.software/api/v1/items/42/price-check \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"id": 5,
"code": "ROSA",
"name": "Rosa roja",
"tax_percent": "16",
"prices": [
{
"price_list_id": 1,
"price_list": "Menudeo",
"uom_id": null,
"uom": "Pieza",
"currency": "MXN",
"price": "100.000000",
"price_with_tax": "116.00"
}
],
"stock": [
{
"warehouse_id": 3,
"warehouse": {
"id": 3,
"code": "MAT",
"name": "Matriz"
},
"on_hand": "12.0000",
"committed": "0.0000",
"available": "12.0000",
"is_working": true,
"uses_bin_locations": true,
"bin_location": {
"id": 7,
"code": "A-01-03",
"description": null
}
}
]
}
}
Referencia
Precios
Calcula el precio de una partida o de un carrito completo con descuentos, promociones e impuestos sin guardar nada.
Cotizar una partida
POST
/pricing/quote
Una partida a partir de texto ("3*ROSA"), código de barras o id de artículo. No guarda nada.
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| document_type | string | sí |
Valores: quotationsales_orderdeliveryreturndown_paymentinvoicecredit_memodebit_memotransfer_requestinventory_transfergoods_receiptgoods_issue
|
| partner_id | integer | no | |
| price_list_id | integer | no | Predeterminado: el del cliente; si no, el del usuario |
| currency_id | integer | no | |
| exchange_rate | number | no | |
| warehouse_id | integer | no | |
| text | string | no | |
| item_id | integer | no | |
| quantity | number | no | |
| uom_id | integer | no |
Respuestas
- 200 Partida cotizada
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/pricing/quote \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"document_type": "sales_order",
"partner_id": 12,
"text": "3*ROSA"
}'
{
"data": {
"item_id": 42,
"item_code": "ROSA",
"description": "…",
"quantity": "232.000000",
"uom_id": 42,
"warehouse_id": 42,
"price": "232.000000",
"discount_percent": "…",
"discount_source": "…",
"promotion_id": 42,
"tax_percent": "…",
"unit_price": "…",
"subtotal": "232.000000",
"tax_total": "…",
"total": "232.000000",
"is_package": false,
"has_choices": false
}
}
Recalcular un carrito
POST
/pricing/recalculate
Todas las partidas de un carrito y sus totales, con promociones. No guarda nada.
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| document_type | string | sí |
Valores: quotationsales_orderdeliveryreturndown_paymentinvoicecredit_memodebit_memotransfer_requestinventory_transfergoods_receiptgoods_issue
|
| partner_id | integer | no | |
| price_list_id | integer | no | Predeterminado: el del cliente; si no, el del usuario |
| currency_id | integer | no | |
| exchange_rate | number | no | |
| warehouse_id | integer | no | |
| discount_percent | number | no | |
| lines | array<object> | sí | |
| lines[].item_id | integer | sí | |
| lines[].quantity | number | sí | |
| lines[].uom_id | integer | no | |
| lines[].price | number | no | |
| lines[].price_is_manual | boolean | no | |
| lines[].discount_percent | number | no | |
| lines[].discount_source | string | no |
Respuestas
- 200 Partidas, totales y errores por partida
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/pricing/recalculate \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"document_type": "sales_order",
"partner_id": 12,
"lines": [
{
"item_id": 5,
"quantity": 2
}
]
}'
{
"data": {
"lines": [
{
"item_id": 5,
"quantity": "2.0000",
"price": "100.000000",
"total": "232.00"
}
],
"totals": {
"subtotal": "200.00",
"tax_total": "32.00",
"total": "232.00"
},
"errors": []
}
}
Referencia
Documentos
Crea, edita y consulta documentos de venta, descarga su PDF o la representación impresa del CFDI y envíalos por correo.
Documentos de venta
GET
/documents
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| type | string | no |
Valores: quotationsales_orderdeliveryreturndown_paymentinvoicecredit_memodebit_memotransfer_requestinventory_transfergoods_receiptgoods_issue
|
| mine | boolean | no | Creados por mí o de mi empleado de ventas |
| status | string | no |
Valores: draftopenclosedcancelled
|
| partner_id | integer | no | |
| from | string<date> | no | |
| to | string<date> | no | |
| search | string | no | |
| per_page | integer | no | Por omisión: 50 Máximo: 200 |
| cursor | string | no | meta.next_cursor de la página anterior |
Respuestas
- 200 Documentos (sin partidas)
- 403 Sobre de error
curl https://demo.nori.software/api/v1/documents?search=manzana&per_page=50 \
-H "Authorization: Bearer $NORI_TOKEN"
{
"links": {},
"meta": {
"path": "…",
"per_page": 1,
"next_cursor": "…",
"prev_cursor": "…"
},
"data": [
{
"id": 81,
"type": "sales_order",
"status": "open",
"folio": "A-120",
"total": "232.000000",
"balance": "232.000000",
"lines": [
{
"id": 301,
"item_id": 5,
"quantity": "2.0000",
"price": "100.000000",
"total": "232.000000"
}
]
}
]
}
Crear un documento de venta
POST
/documents
Idempotency-Key
Lo guarda el motor de documentos: precios, descuentos, impuestos y totales se recalculan y aplica la vista del usuario (los campos bloqueados conservan su valor predeterminado). draft: true lo deja sin contabilizar. Las entregas y facturas con tenders (o con on_credit: true) se contabilizan y cobran en una sola transacción. Con crm_deal_id (una cotización hecha desde una oportunidad del CRM) el documento se liga a la oportunidad.
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| type | string | no |
Obligatorio al crear
Valores: quotationsales_orderdeliveryreturndown_paymentinvoicecredit_memodebit_memo
|
| draft | boolean | no | |
| on_credit | boolean | no | Entregas y facturas: contabilizar y dejar a crédito (aplica la política de crédito) |
| tenders | array<object> | no | |
| tenders[].tender_type_id | integer | sí | |
| tenders[].amount | number | sí | |
| tenders[].reference | string | no | |
| tenders[].credit_memo_id | integer | no | |
| document_series_id | integer | no | |
| business_partner_id | integer | no | |
| contact_id | integer | no | |
| billing_address_id | integer | no | |
| shipping_address_id | integer | no | |
| document_date | string<date> | no | |
| due_date | string<date> | no | |
| delivery_date | string<date> | no | |
| currency_id | integer | no | |
| exchange_rate | number | no | |
| price_list_id | integer | no | |
| payment_terms_id | integer | no | |
| sales_employee_id | integer | no | |
| warehouse_id | integer | no | |
| reference | string | no | |
| notes | string | no | |
| discount_percent | number | no | |
| route_id | integer | no | |
| cfdi_use | string | no | |
| payment_method_id | integer | no | |
| is_reserve | boolean | no | Invoices, on create: reserve invoice (stamped and receivable now; stock moves with the deliveries copied from it) |
| shipping | object | no | Deliveries and invoices: home delivery. enabled false takes a pending one off the logistics board |
| shipping.enabled | boolean | no | |
| shipping.shipping_address_id | integer | no | Defaults to the document shipping address; must be the customer's |
| shipping.references | string | no | |
| shipping.requested_date | string<date> | no | |
| shipping.window_start | string | no | |
| shipping.window_end | string | no | |
| shipping.cash_on_delivery | boolean | no | |
| shipping.latitude | number | no | |
| shipping.longitude | number | no | |
| shipping.notes | string | no | |
| crm_deal_id | integer | no | Solo al crear: la oportunidad del CRM a la que pertenece el documento (mismo cliente; requiere crm_deals.update). Se liga y hace avanzar la oportunidad |
| udf | object | no | User-defined fields: value by field code (U_…). Text, integer, number, date (YYYY-MM-DD), boolean, a list value or a user table row code. See GET /user-fields. |
| lines | array<object> | no | |
| lines[].id | integer | no | Partida existente (edición) |
| lines[].item_id | integer | no | |
| lines[].quantity | number | no | |
| lines[].uom_id | integer | no | |
| lines[].warehouse_id | integer | no | |
| lines[].price | number | no | |
| lines[].price_is_manual | boolean | no | |
| lines[].discount_percent | number | no | |
| lines[].base_line_id | integer | no | Copiada de esta partida de un documento base |
| lines[].notes | string | no | |
| lines[].udf | object | no | User-defined fields: value by field code (U_…). Text, integer, number, date (YYYY-MM-DD), boolean, a list value or a user table row code. See GET /user-fields. |
| lines[].choices | object | no | Opciones del paquete: {id de grupo: {id de artículo: cuántos}} por unidad |
Respuestas
- 201 Creado
- 403 Sobre de error
- 422 Sobre de error
- 428 Sobre de error
curl -X POST https://demo.nori.software/api/v1/documents \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"type": "sales_order",
"business_partner_id": 12,
"delivery_date": "2026-11-05",
"lines": [
{
"item_id": 5,
"quantity": 2
}
]
}'
{
"data": {
"id": 42,
"type": "quotation",
"status": "draft",
"is_reserve": false,
"shipment": {
"id": 42,
"status": "pending",
"requested_date": "2026-11-03",
"window": "…",
"cash_on_delivery": false,
"scheduled_date": "2026-11-03",
"delivered_at": "2026-11-03T10:15:00-06:00",
"received_by": "…",
"failure_reason": "…"
},
"folio": "A-1043",
"series": "…",
"number": 1,
"business_partner_id": 42,
"partner": {},
"partner_name": "…",
"document_date": "2026-11-03",
"due_date": "2026-11-03",
"delivery_date": "2026-11-03",
"currency_id": 42,
"exchange_rate": "…",
"price_list_id": 42,
"sales_employee_id": 42,
"warehouse_id": 42,
"to_warehouse_id": 42,
"causality_id": 42,
"reference": "A-1043",
"notes": "…",
"subtotal": "232.000000",
"discount_total": "…",
"tax_total": "…",
"total": "232.000000",
"paid_total": "…",
"balance": "232.000000",
"udf": {
"U_RUTA": "N",
"U_PESO": 12.5,
"U_REVISADO": true
},
"lines": [
{
"id": 42,
"line_number": 1,
"item_id": 42,
"item_code": "ROSA",
"description": "…",
"quantity": "232.000000",
"open_quantity": "…",
"uom_id": 42,
"warehouse_id": 42,
"price": "232.000000",
"discount_percent": "…",
"tax_percent": "…",
"subtotal": "232.000000",
"tax_total": "…",
"total": "232.000000",
"base_line_id": 42,
"is_closed": false,
"udf": {
"U_RUTA": "N",
"U_PESO": 12.5,
"U_REVISADO": true
},
"components": [
null
]
}
],
"pdf_url": "https://demo.nori.software/…",
"posted_at": "2026-11-03T10:15:00-06:00",
"created_at": "2026-11-03T10:15:00-06:00",
"payment": {
"id": 40,
"type": "incoming",
"folio": "P-15",
"total": "232.000000",
"change_total": "0.000000",
"applications": [
{
"document_id": 81,
"folio": "A-120",
"type": "invoice",
"amount": "232.000000"
}
]
}
}
}
Documento con sus partidas
GET
/documents/{document}
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| document | integer | sí |
Respuestas
- 200 Documento
- 404 Sobre de error
curl https://demo.nori.software/api/v1/documents/42 \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"id": 81,
"type": "sales_order",
"status": "open",
"folio": "A-120",
"total": "232.000000",
"balance": "232.000000",
"lines": [
{
"id": 301,
"item_id": 5,
"quantity": "2.0000",
"price": "100.000000",
"total": "232.000000"
}
]
}
}
Editar un documento
PATCH
/documents/{document}
Idempotency-Key
Edita un borrador (y lo contabiliza con post=true) o los datos de encabezado de un documento contabilizado.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| document | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| type | string | no |
Obligatorio al crear
Valores: quotationsales_orderdeliveryreturndown_paymentinvoicecredit_memodebit_memo
|
| draft | boolean | no | |
| on_credit | boolean | no | Entregas y facturas: contabilizar y dejar a crédito (aplica la política de crédito) |
| tenders | array<object> | no | |
| tenders[].tender_type_id | integer | sí | |
| tenders[].amount | number | sí | |
| tenders[].reference | string | no | |
| tenders[].credit_memo_id | integer | no | |
| document_series_id | integer | no | |
| business_partner_id | integer | no | |
| contact_id | integer | no | |
| billing_address_id | integer | no | |
| shipping_address_id | integer | no | |
| document_date | string<date> | no | |
| due_date | string<date> | no | |
| delivery_date | string<date> | no | |
| currency_id | integer | no | |
| exchange_rate | number | no | |
| price_list_id | integer | no | |
| payment_terms_id | integer | no | |
| sales_employee_id | integer | no | |
| warehouse_id | integer | no | |
| reference | string | no | |
| notes | string | no | |
| discount_percent | number | no | |
| route_id | integer | no | |
| cfdi_use | string | no | |
| payment_method_id | integer | no | |
| is_reserve | boolean | no | Invoices, on create: reserve invoice (stamped and receivable now; stock moves with the deliveries copied from it) |
| shipping | object | no | Deliveries and invoices: home delivery. enabled false takes a pending one off the logistics board |
| shipping.enabled | boolean | no | |
| shipping.shipping_address_id | integer | no | Defaults to the document shipping address; must be the customer's |
| shipping.references | string | no | |
| shipping.requested_date | string<date> | no | |
| shipping.window_start | string | no | |
| shipping.window_end | string | no | |
| shipping.cash_on_delivery | boolean | no | |
| shipping.latitude | number | no | |
| shipping.longitude | number | no | |
| shipping.notes | string | no | |
| crm_deal_id | integer | no | Solo al crear: la oportunidad del CRM a la que pertenece el documento (mismo cliente; requiere crm_deals.update). Se liga y hace avanzar la oportunidad |
| udf | object | no | User-defined fields: value by field code (U_…). Text, integer, number, date (YYYY-MM-DD), boolean, a list value or a user table row code. See GET /user-fields. |
| lines | array<object> | no | |
| lines[].id | integer | no | Partida existente (edición) |
| lines[].item_id | integer | no | |
| lines[].quantity | number | no | |
| lines[].uom_id | integer | no | |
| lines[].warehouse_id | integer | no | |
| lines[].price | number | no | |
| lines[].price_is_manual | boolean | no | |
| lines[].discount_percent | number | no | |
| lines[].base_line_id | integer | no | Copiada de esta partida de un documento base |
| lines[].notes | string | no | |
| lines[].udf | object | no | User-defined fields: value by field code (U_…). Text, integer, number, date (YYYY-MM-DD), boolean, a list value or a user table row code. See GET /user-fields. |
| lines[].choices | object | no | Opciones del paquete: {id de grupo: {id de artículo: cuántos}} por unidad |
| post | boolean | no |
Respuestas
- 200 Actualizado
- 422 Sobre de error
curl -X PATCH https://demo.nori.software/api/v1/documents/42 \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"reference": "OC-9",
"post": true
}'
{
"data": {
"id": 81,
"type": "sales_order",
"status": "open",
"folio": "A-120",
"total": "232.000000",
"balance": "232.000000",
"lines": [
{
"id": 301,
"item_id": 5,
"quantity": "2.0000",
"price": "100.000000",
"total": "232.000000"
}
]
}
}
PDF del documento
GET
/documents/{document}/pdf
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| document | integer | sí |
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| template | integer | no |
Respuestas
- 200 PDF
- 503 Sobre de error
curl https://demo.nori.software/api/v1/documents/42/pdf \
-H "Authorization: Bearer $NORI_TOKEN"
La respuesta es un archivo (application/pdf).
Representación impresa del CFDI
GET
/documents/{document}/cfdi
Se lee del XML timbrado: comprobante, emisor, receptor, conceptos con sus impuestos, impuestos por impuesto y tasa, totales con el importe con letra, el timbre (UUID, certificados, sellos, cadena original del complemento de certificación del SAT) y la URL de verificación del SAT para imprimir como QR. Facturas, notas de crédito y de cargo y anticipos ("I"/"E"); entregas y traslados con Carta Porte ("T", carta_porte). Un documento que aún no se timbra responde su vista previa con status: unstamped (stamp y verification_url en null; imprime "SIN TIMBRAR · No válido como comprobante fiscal" cuando fiscal es true). Un CFDI cancelado responde status: cancelled con cancelled_at.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| document | integer | sí |
Respuestas
- 200 Datos de impresión del CFDI
- 404 Sobre de error
curl https://demo.nori.software/api/v1/documents/42/cfdi \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"status": "stamped",
"stamped": false,
"fiscal": false,
"cancelled": false,
"cancelled_at": "2026-11-03T10:15:00-06:00",
"title": "Factura",
"version": "4.0",
"series": "A",
"folio": "120",
"date": "2026-10-22T10:15:00",
"place_of_issue": "42501",
"voucher_type": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"export": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"payment_form": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"payment_method": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"currency": "MXN",
"exchange_rate": "…",
"payment_terms": "…",
"issuer": {
"rfc": "XAXX010101000",
"name": "Rosa roja",
"tax_regime": {
"code": "601",
"name": "General de Ley Personas Morales"
}
},
"receiver": {
"rfc": "XAXX010101000",
"name": "Rosa roja",
"tax_address": "…",
"tax_regime": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"cfdi_use": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"foreign_tax_id": "…",
"tax_residence": "…"
},
"global_information": {
"periodicity": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"months": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"year": "…"
},
"related": [
{
"relation_type": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"uuid": "…"
}
],
"concepts": [
{
"product_code": "…",
"identification": "…",
"quantity": "232.000000",
"unit_code": "…",
"unit": "Pieza",
"description": "…",
"unit_price": "…",
"amount": "232.000000",
"discount": "…",
"tax_object": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"taxes": [
null
]
}
],
"subtotal": "232.000000",
"discount": "…",
"taxes": [
{
"kind": "transfer",
"tax": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"factor": "Tasa",
"rate": "0.160000",
"base": "300.00",
"amount": "48.00",
"label": "IVA trasladado 16 %"
}
],
"total_transferred": "…",
"total_withheld": "…",
"total": "232.000000",
"amount_in_words": "(TRESCIENTOS CUARENTA Y OCHO PESOS 00/100 M.N.)",
"stamp": {
"uuid": "…",
"stamped_at": "2026-11-03T10:15:00-06:00",
"pac_rfc": "…",
"sat_certificate": "…",
"issuer_certificate": "…",
"cfd_seal": "…",
"sat_seal": "…",
"original_string": "…"
},
"issuer_certificate": "…",
"verification_url": "https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx?id=…&re=…&rr=…&tt=348.0&fe=…",
"legend": "Este documento es una representación impresa de un CFDI",
"payments": [
{
"date": "2026-11-03",
"form": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"currency": "MXN",
"exchange_rate": "…",
"amount": "232.000000",
"operation_number": "…",
"taxes": [
null
],
"documents": [
null
]
}
],
"payment_totals": {
"total": "232.000000",
"lines": [
{
"name": null,
"label": null,
"amount": null
}
]
},
"carta_porte": {},
"notes": "…"
}
}
PDF del documento por enlace firmado
GET
/documents/{document}/pdf/signed
Sin sesión
PDF desde el pdf_url firmado del recurso (válido 30 minutos, sin token).
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| document | integer | sí |
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| expires | integer | sí | |
| signature | string | sí |
Respuestas
- 200 PDF
- 403 Sobre de error
curl https://demo.nori.software/api/v1/documents/42/pdf/signed?expires=valor&signature=valor
La respuesta es un archivo (application/pdf).
Enviar el documento por correo
POST
/documents/{document}/email
Idempotency-Key
Envía el documento con su PDF (y el XML si es un CFDI timbrado).
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| document | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| to | array<string<email>> | no | Predeterminado: el socio de negocio y el contacto del documento |
| subject | string | no | |
| message | string | no | |
| copy_me | boolean | no |
Respuestas
- 202 Enviado o en cola
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/documents/42/email \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"to": [
"compras@cliente.mx"
],
"message": "Le enviamos su pedido."
}'
{
"data": {
"id": 42,
"status": "…",
"to": [
"…"
]
}
}
Referencia
Pagos
Registra cobros contra documentos abiertos o a cuenta, consulta pagos y obtén el recibo o el complemento de pago.
Cobros recibidos
GET
/payments
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| mine | boolean | no | |
| partner_id | integer | no | |
| from | string<date> | no | |
| to | string<date> | no | |
| per_page | integer | no | Por omisión: 50 Máximo: 200 |
| cursor | string | no | meta.next_cursor de la página anterior |
Respuestas
- 200 Pagos
curl https://demo.nori.software/api/v1/payments?per_page=50 \
-H "Authorization: Bearer $NORI_TOKEN"
{
"links": {},
"meta": {
"path": "…",
"per_page": 1,
"next_cursor": "…",
"prev_cursor": "…"
},
"data": [
{
"id": 40,
"type": "incoming",
"folio": "P-15",
"total": "232.000000",
"change_total": "0.000000",
"applications": [
{
"document_id": 81,
"folio": "A-120",
"type": "invoice",
"amount": "232.000000"
}
]
}
]
}
Registrar un cobro
POST
/payments
Idempotency-Key
Se registra con formas de pago (el efectivo da cambio, se pide referencia cuando la forma de pago la requiere, las notas de crédito sirven como forma de pago) y aplicaciones por documento (amount: null = lo que aún debe). La caja del usuario debe estar abierta para el dinero que entra al cajón. Con on_account: true no necesita documentos y el sobrante queda como saldo sin aplicar del cliente (unapplied_total).
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| business_partner_id | integer | sí | |
| on_account | boolean | no | Pago a cuenta: no requiere documentos; el resto queda como saldo sin aplicar |
| payment_date | string<date> | no | |
| reference | string | no | |
| notes | string | no | |
| udf | object | no | User-defined fields: value by field code (U_…). Text, integer, number, date (YYYY-MM-DD), boolean, a list value or a user table row code. See GET /user-fields. |
| applications | array<object> | no | |
| applications[].document_id | integer | sí | |
| applications[].amount | number | null | no | null = lo que el documento aún debe |
| tenders | array<object> | no | |
| tenders[].tender_type_id | integer | sí | |
| tenders[].amount | number | sí | |
| tenders[].reference | string | no | |
| tenders[].credit_memo_id | integer | no |
Respuestas
- 201 Registrado
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/payments \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"business_partner_id": 12,
"applications": [
{
"document_id": 81,
"amount": null
}
],
"tenders": [
{
"tender_type_id": 1,
"amount": 232
}
],
"reference": "Cobro ruta"
}'
{
"data": {
"id": 40,
"type": "incoming",
"folio": "P-15",
"total": "232.000000",
"change_total": "0.000000",
"applications": [
{
"document_id": 81,
"folio": "A-120",
"type": "invoice",
"amount": "232.000000"
}
]
}
}
Pago con formas de pago y aplicaciones
GET
/payments/{payment}
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| payment | integer | sí |
Respuestas
- 200 Pago
- 404 Sobre de error
curl https://demo.nori.software/api/v1/payments/42 \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"id": 40,
"type": "incoming",
"folio": "P-15",
"total": "232.000000",
"change_total": "0.000000",
"applications": [
{
"document_id": 81,
"folio": "A-120",
"type": "invoice",
"amount": "232.000000"
}
]
}
}
PDF del recibo de pago
GET
/payments/{payment}/pdf
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| payment | integer | sí |
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| template | integer | no |
Respuestas
- 200 PDF
- 503 Sobre de error
curl https://demo.nori.software/api/v1/payments/42/pdf \
-H "Authorization: Bearer $NORI_TOKEN"
La respuesta es un archivo (application/pdf).
Representación impresa del complemento de pago
GET
/payments/{payment}/cfdi
Mismo contrato que /documents/{document}/cfdi; payments trae cada pago con sus documentos relacionados (UUID, serie-folio, parcialidad, saldo anterior, pagado, saldo insoluto, objeto de impuesto) y payment_totals los totales del complemento. Un pago que no requiere complemento (PUE, venta de contado) responde fiscal: false. Los pagos efectuados responden 404.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| payment | integer | sí |
Respuestas
- 200 Datos de impresión del CFDI
- 404 Sobre de error
curl https://demo.nori.software/api/v1/payments/42/cfdi \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"status": "stamped",
"stamped": false,
"fiscal": false,
"cancelled": false,
"cancelled_at": "2026-11-03T10:15:00-06:00",
"title": "Factura",
"version": "4.0",
"series": "A",
"folio": "120",
"date": "2026-10-22T10:15:00",
"place_of_issue": "42501",
"voucher_type": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"export": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"payment_form": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"payment_method": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"currency": "MXN",
"exchange_rate": "…",
"payment_terms": "…",
"issuer": {
"rfc": "XAXX010101000",
"name": "Rosa roja",
"tax_regime": {
"code": "601",
"name": "General de Ley Personas Morales"
}
},
"receiver": {
"rfc": "XAXX010101000",
"name": "Rosa roja",
"tax_address": "…",
"tax_regime": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"cfdi_use": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"foreign_tax_id": "…",
"tax_residence": "…"
},
"global_information": {
"periodicity": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"months": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"year": "…"
},
"related": [
{
"relation_type": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"uuid": "…"
}
],
"concepts": [
{
"product_code": "…",
"identification": "…",
"quantity": "232.000000",
"unit_code": "…",
"unit": "Pieza",
"description": "…",
"unit_price": "…",
"amount": "232.000000",
"discount": "…",
"tax_object": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"taxes": [
null
]
}
],
"subtotal": "232.000000",
"discount": "…",
"taxes": [
{
"kind": "transfer",
"tax": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"factor": "Tasa",
"rate": "0.160000",
"base": "300.00",
"amount": "48.00",
"label": "IVA trasladado 16 %"
}
],
"total_transferred": "…",
"total_withheld": "…",
"total": "232.000000",
"amount_in_words": "(TRESCIENTOS CUARENTA Y OCHO PESOS 00/100 M.N.)",
"stamp": {
"uuid": "…",
"stamped_at": "2026-11-03T10:15:00-06:00",
"pac_rfc": "…",
"sat_certificate": "…",
"issuer_certificate": "…",
"cfd_seal": "…",
"sat_seal": "…",
"original_string": "…"
},
"issuer_certificate": "…",
"verification_url": "https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx?id=…&re=…&rr=…&tt=348.0&fe=…",
"legend": "Este documento es una representación impresa de un CFDI",
"payments": [
{
"date": "2026-11-03",
"form": {
"code": "601",
"name": "General de Ley Personas Morales"
},
"currency": "MXN",
"exchange_rate": "…",
"amount": "232.000000",
"operation_number": "…",
"taxes": [
null
],
"documents": [
null
]
}
],
"payment_totals": {
"total": "232.000000",
"lines": [
{
"name": null,
"label": null,
"amount": null
}
]
},
"carta_porte": {},
"notes": "…"
}
}
PDF del recibo por enlace firmado
GET
/payments/{payment}/pdf/signed
Sin sesión
PDF desde el pdf_url firmado (sin token).
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| payment | integer | sí |
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| expires | integer | sí | |
| signature | string | sí |
Respuestas
- 200 PDF
- 403 Sobre de error
curl https://demo.nori.software/api/v1/payments/42/pdf/signed?expires=valor&signature=valor
La respuesta es un archivo (application/pdf).
Enviar el recibo por correo
POST
/payments/{payment}/email
Idempotency-Key
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| payment | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| to | array<string<email>> | no | Predeterminado: el socio de negocio y el contacto del documento |
| subject | string | no | |
| message | string | no | |
| copy_me | boolean | no |
Respuestas
- 202 Enviado o en cola
curl -X POST https://demo.nori.software/api/v1/payments/42/email \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"to": [
"ana@example.com"
],
"subject": "…",
"message": "Listo.",
"copy_me": false
}'
{
"data": {
"id": 42,
"status": "…",
"to": [
"…"
]
}
}
Referencia
Inventario
Consulta existencias al escanear, revisa lotes por caducar y registra solicitudes de traslado, traslados, entradas y salidas de mercancía.
Lotes con existencia
GET
/items/{item}/batches
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| item | integer | sí |
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| warehouse_ids[] | array<integer> | no |
Respuestas
- 200 Lotes
curl https://demo.nori.software/api/v1/items/42/batches \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"batch_id": 42,
"number": "…",
"supplier_batch": "…",
"internal_number": "…",
"availability": "released",
"manufactured_on": "2026-11-03",
"expires_on": "2026-11-03",
"received_on": "2026-11-03",
"is_expired": false,
"warehouse_id": 42,
"warehouse": "…",
"bin_location_id": 42,
"bin_code": "…",
"on_hand": "232.000000"
}
]
}
Números de serie del artículo
GET
/items/{item}/serial-numbers
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| item | integer | sí |
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| warehouse_ids[] | array<integer> | no | |
| status | string | no |
Valores: in_stockissuedall
Por omisión: in_stock
|
| search | string | no |
Respuestas
- 200 Números de serie (hasta 500)
curl https://demo.nori.software/api/v1/items/42/serial-numbers?search=manzana \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"serial_number_id": 42,
"number": "…",
"status": "in_stock",
"warehouse_id": 42,
"warehouse": "…",
"bin_location_id": 42,
"bin_code": "…",
"batch_number": "…",
"expires_on": "2026-11-03",
"manufacturer_serial": "…",
"lot_number": "…",
"warranty_ends_on": "2026-11-03",
"availability": "released"
}
]
}
Existencias por ubicación
GET
/items/{item}/bin-stock
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| item | integer | sí |
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| warehouse_ids[] | array<integer> | no |
Respuestas
- 200 Existencias por ubicación
curl https://demo.nori.software/api/v1/items/42/bin-stock \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"warehouse_id": 42,
"warehouse": "…",
"bin_location_id": 42,
"code": "ROSA",
"description": "…",
"on_hand": "232.000000"
}
]
}
Ubicaciones activas de un almacén
GET
/warehouses/{warehouse}/bins
Ubicaciones tal como las lleva el ERP (subniveles, código de barras, atributos, restricción, mínimo y máximo). Con la integración al ERP activa, se heredan de él.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| warehouse | integer | sí |
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| code | string | no | Solo la ubicación con este código o código de barras (al escanear la etiqueta del anaquel) |
Respuestas
- 200 Ubicaciones; meta.uses_bin_locations indica si el almacén lleva existencias por ubicación
- 403 Sobre de error
curl https://demo.nori.software/api/v1/warehouses/42/bins \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"id": 42,
"code": "ROSA",
"description": "…",
"is_default": false,
"sublevels": [
"…"
],
"barcode": "7501055363057",
"attributes": {},
"restriction": "none",
"is_receiving": false,
"minimum_quantity": "…",
"maximum_quantity": "…"
}
],
"meta": {
"uses_bin_locations": false,
"enforce_default_bin": false
}
}
Contenido de una ubicación
GET
/bins/{bin}/stock
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| bin | integer | sí |
Respuestas
- 200 Contenido de la ubicación
- 403 Sobre de error
curl https://demo.nori.software/api/v1/bins/42/stock \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"item_id": 42,
"code": "ROSA",
"name": "Rosa roja",
"batch_id": 42,
"batch_number": "…",
"expires_on": "2026-11-03",
"on_hand": "232.000000"
}
],
"meta": {}
}
Consultar un artículo con su existencia
GET
/inventory/lookup
Escanea o escribe un artículo ("3*CODIGO", código de barras, etiqueta de báscula) y obtén su existencia.
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| text | string | no | |
| item_id | integer | no | |
| warehouse_ids[] | array<integer> | no |
Respuestas
- 200 Artículo, cantidad y saldos por almacén
- 422 Sobre de error
curl https://demo.nori.software/api/v1/inventory/lookup \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"item": {
"id": 5,
"code": "ROSA",
"name": "Rosa roja",
"uom_factor": "1"
},
"quantity": "2.0000",
"balances": {
"3": {
"on_hand": "15.0000",
"committed": "0.0000",
"ordered": "0.0000",
"available": "15.0000"
}
}
}
}
Lotes vencidos o por vencer
GET
/inventory/expiring
Lotes con existencia que ya vencieron o vencen dentro de los días indicados (el valor de la empresa si se omite).
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| days | integer | no | |
| warehouse_ids[] | array<integer> | no |
Respuestas
- 200 Lotes por vencer, los más próximos primero
curl https://demo.nori.software/api/v1/inventory/expiring \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"item_id": 42,
"code": "ROSA",
"name": "Rosa roja",
"batch_id": 42,
"number": "…",
"expires_on": "2026-11-03",
"days_left": 1,
"is_expired": false,
"warehouse_id": 42,
"warehouse": "…",
"bin_code": "…",
"on_hand": "232.000000"
}
],
"meta": {
"days": 1
}
}
Solicitudes de traslado pendientes
GET
/inventory/transfer-requests/pending
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| warehouse_id | integer | no | |
| limit | integer | no | Por omisión: 50 |
Respuestas
- 200 Solicitudes
curl https://demo.nori.software/api/v1/inventory/transfer-requests/pending \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"id": 42,
"folio": "A-1043",
"date": "2026-11-03",
"days": 1,
"from": "…",
"to": "…",
"reference": "A-1043",
"lines": 1,
"requested": "…",
"transferred": "…",
"pending": "…",
"progress": 1
}
]
}
Partidas abiertas de una solicitud
GET
/inventory/transfer-requests/{document}/open-lines
Partidas abiertas de una solicitud, listas para trasladar (base_line_id).
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| document | integer | sí |
Respuestas
- 200 Partidas y saldos
- 422 Sobre de error
curl https://demo.nori.software/api/v1/inventory/transfer-requests/42/open-lines \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {}
}
Registrar un movimiento de inventario
POST
/inventory/{movement}
Idempotency-Key
Solicitud de traslado, traslado, entrada o salida de mercancía. Las entradas y salidas requieren una causa de su sentido; las entradas, el costo por unidad. Un traslado desde una solicitud envía base_line_id en cada partida.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| movement | string | sí |
Valores: transfer-requeststransfersgoods-receiptsgoods-issues
|
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| draft | boolean | no | |
| document_series_id | integer | no | |
| document_date | string<date> | no | |
| warehouse_id | integer | sí | |
| to_warehouse_id | integer | no | |
| causality_id | integer | no | |
| reference | string | no | |
| notes | string | no | |
| udf | object | no | User-defined fields: value by field code (U_…). Text, integer, number, date (YYYY-MM-DD), boolean, a list value or a user table row code. See GET /user-fields. |
| lines | array<object> | sí | |
| lines[].item_id | integer | sí | |
| lines[].quantity | number | sí | |
| lines[].uom_id | integer | no | |
| lines[].warehouse_id | integer | no | |
| lines[].to_warehouse_id | integer | no | |
| lines[].cost | number | no | Entradas de mercancía: costo por unidad (obligatorio) |
| lines[].base_line_id | integer | no | Traslados: partida de la solicitud |
| lines[].udf | object | no | User-defined fields: value by field code (U_…). Text, integer, number, date (YYYY-MM-DD), boolean, a list value or a user table row code. See GET /user-fields. |
| lines[].allocations | array<object> | no | Lotes, números de serie y ubicaciones de la partida |
| lines[].allocations[].item_id | integer | no | Un componente de un kit; si se omite, el artículo de la partida |
| lines[].allocations[].batch_id | integer | no | |
| lines[].allocations[].batch_number | string | no | Entradas: se crea un lote nuevo con él |
| lines[].allocations[].serial_number_id | integer | no | |
| lines[].allocations[].serial_number | string | no | |
| lines[].allocations[].bin_location_id | integer | no | Ubicación a la que entra (entradas) o de la que sale (salidas, traslados) |
| lines[].allocations[].to_bin_location_id | integer | no | Traslados: ubicación destino (mismo almacén = de ubicación a ubicación) |
| lines[].allocations[].supplier_batch | string | no | |
| lines[].allocations[].manufactured_on | string<date> | no | |
| lines[].allocations[].expires_on | string<date> | no | |
| lines[].allocations[].quantity | number | no |
Respuestas
- 201 Documento guardado
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/inventory/42 \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"warehouse_id": 3,
"causality_id": 2,
"lines": [
{
"item_id": 5,
"quantity": 2
}
]
}'
{
"data": {
"id": 81,
"type": "sales_order",
"status": "open",
"folio": "A-120",
"total": "232.000000",
"balance": "232.000000",
"lines": [
{
"id": 301,
"item_id": 5,
"quantity": "2.0000",
"price": "100.000000",
"total": "232.000000"
}
]
}
}
Referencia
Conteos físicos
Únete a un conteo de inventario, registra lo encontrado por artículo, lote o ubicación y revisa el avance.
Conteos físicos
GET
/inventory/counts
Conteos (en curso por omisión) con su avance.
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| status | string | no |
Valores: countingpostedcancelled
|
| warehouse_id | integer | no |
Respuestas
- 200 Conteos
curl https://demo.nori.software/api/v1/inventory/counts \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"id": 42,
"number": 1,
"title": "…",
"name": "Rosa roja",
"warehouse": {},
"status": "counting",
"is_blind": false,
"freezes_stock": false,
"snapshot_at": "2026-11-03T10:15:00-06:00",
"notes": "…",
"progress": {
"lines": 1,
"counted": 1,
"progress": 1
}
}
]
}
Unirse a un conteo
GET
/inventory/counts/{count}
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| count | integer | sí |
Respuestas
- 200 Conteo
curl https://demo.nori.software/api/v1/inventory/counts/42 \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"id": 42,
"number": 1,
"title": "…",
"name": "Rosa roja",
"warehouse": {},
"status": "counting",
"is_blind": false,
"freezes_stock": false,
"snapshot_at": "2026-11-03T10:15:00-06:00",
"notes": "…",
"progress": {
"lines": 1,
"counted": 1,
"progress": 1
}
}
}
Avance del conteo
GET
/inventory/counts/{count}/progress
Partidas totales y partidas contadas.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| count | integer | sí |
Respuestas
- 200 Avance
curl https://demo.nori.software/api/v1/inventory/counts/42/progress \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"lines": 1,
"counted": 1,
"progress": 1
}
}
Partidas del conteo
GET
/inventory/counts/{count}/lines
Partidas del conteo (la cantidad en sistema se oculta en un conteo a ciegas).
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| count | integer | sí |
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| counted | boolean | no | |
| per_page | integer | no | Por omisión: 50 Máximo: 200 |
| cursor | string | no | meta.next_cursor de la página anterior |
Respuestas
- 200 Partidas
curl https://demo.nori.software/api/v1/inventory/counts/42/lines?per_page=50 \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"id": 42,
"item_id": 42,
"code": "ROSA",
"name": "Rosa roja",
"uom": "Pieza",
"system": "…",
"counted": "…",
"counted_at": "2026-11-03T10:15:00-06:00",
"is_extra": false
}
],
"meta": {
"next_cursor": "…",
"per_page": 1
}
}
Últimas capturas
GET
/inventory/counts/{count}/entries
Últimas capturas (solo las mías con mine=1).
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| count | integer | sí |
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| mine | boolean | no | |
| limit | integer | no | Por omisión: 100 |
Respuestas
- 200 Capturas
curl https://demo.nori.software/api/v1/inventory/counts/42/entries \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"id": 42,
"line_id": 42,
"item_id": 42,
"code": "ROSA",
"name": "Rosa roja",
"quantity": "232.000000",
"counted_after": "…",
"user_id": 42,
"user": "…",
"created_at": "2026-11-03T10:15:00-06:00"
}
]
}
Registrar lo contado
POST
/inventory/counts/{count}/entries
Idempotency-Key
Registra lo encontrado (el escaneo suma; mode=set reemplaza).
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| count | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| text | string | no | Escaneo o "3*CODIGO"; el código de barras de una caja cuenta sus unidades |
| item_id | integer | no | |
| quantity | number | no | |
| mode | string | no |
Valores: addset
Por omisión: add
|
| bin_location_id | integer | no | Ubicación contada (almacenes con ubicaciones) |
| batch_id | integer | no | Lote contado (artículos con lote) |
| batch_number | string | no | Lote contado por número; se crea si no existe |
| expires_on | string<date> | no | Caducidad de un lote encontrado durante el conteo |
Respuestas
- 201 Partida y avance
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/inventory/counts/42/entries \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"text": "3*ROSA"
}'
{
"data": {
"line": {
"id": 42,
"item_id": 42,
"code": "ROSA",
"name": "Rosa roja",
"uom": "Pieza",
"system": "…",
"counted": "…",
"counted_at": "2026-11-03T10:15:00-06:00",
"is_extra": false
},
"progress": {
"lines": 1,
"counted": 1,
"progress": 1
}
}
}
Deshacer la última captura
DELETE
/inventory/counts/{count}/lines/{line}/last-entry
Idempotency-Key
Revierte la última captura de una partida.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| count | integer | sí | |
| line | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Respuestas
- 200 Partida y avance
curl -X DELETE https://demo.nori.software/api/v1/inventory/counts/42/lines/3/last-entry \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
{
"data": {
"line": {
"id": 42,
"item_id": 42,
"code": "ROSA",
"name": "Rosa roja",
"uom": "Pieza",
"system": "…",
"counted": "…",
"counted_at": "2026-11-03T10:15:00-06:00",
"is_extra": false
},
"progress": {
"lines": 1,
"counted": 1,
"progress": 1
}
}
}
Referencia
Venta en ruta
Opera el día de ruta: inicio y cierre, visitas, ventas, pedidos, entregas, cobros, recolecciones, servicio a equipos y fotos.
Rutas del día
GET
/route/today
Las rutas del día del usuario con paradas, motivos y formas de pago.
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| date | string<date> | no |
Respuestas
- 200 Planes y catálogos (sin envolver en data)
- 403 Sobre de error
curl https://demo.nori.software/api/v1/route/today \
-H "Authorization: Bearer $NORI_TOKEN"
{
"date": "2026-11-03",
"plans": [
{}
],
"reasons": {},
"tenders": [
{}
],
"server_time": "2026-11-03T10:15:00-06:00"
}
Un día de ruta
GET
/route/plans/{plan}
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| plan | integer | sí |
Respuestas
- 200 Plan (sin envolver)
curl https://demo.nori.software/api/v1/route/plans/42 \
-H "Authorization: Bearer $NORI_TOKEN"
{
"plan": {},
"replayed": false
}
Carga del camión
GET
/route/plans/{plan}/stock
Lo que lleva el camión.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| plan | integer | sí |
Respuestas
- 200 Artículos en el camión
curl https://demo.nori.software/api/v1/route/plans/42/stock \
-H "Authorization: Bearer $NORI_TOKEN"
{
"items": [
{}
]
}
Iniciar el día
POST
/route/plans/{plan}/start
Idempotency-Key
Inicia el día (abre la caja del vendedor con el fondo).
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| plan | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| fund | number | no |
Respuestas
- 200 Plan (sin envolver)
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/route/plans/42/start \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"fund": 200
}'
{
"plan": {},
"replayed": false
}
Regreso al almacén
POST
/route/plans/{plan}/finish
Idempotency-Key
Cierra el día al volver al almacén.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| plan | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Respuestas
- 200 Plan (sin envolver)
curl -X POST https://demo.nori.software/api/v1/route/plans/42/finish \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
{
"plan": {},
"replayed": false
}
Agregar una parada no planeada
POST
/route/plans/{plan}/visits
Idempotency-Key
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| plan | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| business_partner_id | integer | sí | |
| reason_ids | array<integer> | no |
Respuestas
- 200 Visita (sin envolver)
curl -X POST https://demo.nori.software/api/v1/route/plans/42/visits \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"business_partner_id": 42,
"reason_ids": [
42
]
}'
{
"visit": {},
"replayed": false
}
Clientes para una parada no planeada
GET
/route/customers
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| search | string | no |
Respuestas
- 200 Clientes
curl https://demo.nori.software/api/v1/route/customers?search=manzana \
-H "Authorization: Bearer $NORI_TOKEN"
[
{
"id": 42,
"label": "…",
"sublabel": "…"
}
]
Detalle de una parada
GET
/route/visits/{visit}
Una parada con pedidos por entregar, saldo, equipos, sugerido y lo que ya se hizo.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| visit | integer | sí |
Respuestas
- 200 Visita (sin envolver)
- 403 Sobre de error
curl https://demo.nori.software/api/v1/route/visits/42 \
-H "Authorization: Bearer $NORI_TOKEN"
{
"visit": {},
"replayed": false
}
Registrar llegada
POST
/route/visits/{visit}/check-in
Idempotency-Key
Llegada con GPS y hora del dispositivo.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| visit | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| lat | number | no | |
| lng | number | no | |
| at | string<date-time> | no | Hora del dispositivo |
Respuestas
- 200 Visita (sin envolver)
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/route/visits/42/check-in \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"lat": 20.6736,
"lng": -103.344,
"at": "2026-11-03T09:12:00-06:00"
}'
{
"visit": {},
"replayed": false
}
Registrar salida
POST
/route/visits/{visit}/check-out
Idempotency-Key
Salida de la parada (una parada de venta sin venta necesita antes un motivo de no venta).
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| visit | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| lat | number | no | |
| lng | number | no | |
| at | string<date-time> | no | Hora del dispositivo |
| notes | string | no |
Respuestas
- 200 Visita (sin envolver)
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/route/visits/42/check-out \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"lat": 1.5,
"lng": 1.5,
"at": "2026-11-03T10:15:00-06:00",
"notes": "…"
}'
{
"visit": {},
"replayed": false
}
Parada no visitada
POST
/route/visits/{visit}/skip
Idempotency-Key
Marca la parada como no visitada, con motivo.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| visit | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| reason_id | integer | sí | |
| notes | string | no |
Respuestas
- 200 Visita (sin envolver)
curl -X POST https://demo.nori.software/api/v1/route/visits/42/skip \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"reason_id": 42,
"notes": "…"
}'
{
"visit": {},
"replayed": false
}
Visita sin venta
POST
/route/visits/{visit}/no-sale
Idempotency-Key
Visitado sin venta, con motivo.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| visit | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| reason_id | integer | sí | |
| notes | string | no |
Respuestas
- 200 Visita (sin envolver)
curl -X POST https://demo.nori.software/api/v1/route/visits/42/no-sale \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"reason_id": 42,
"notes": "…"
}'
{
"visit": {},
"replayed": false
}
Cotizar el carrito
POST
/route/visits/{visit}/quote
Precios y totales del motor para un carrito antes de vender.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| visit | integer | sí |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| kind | string | no |
Valores: saleorderpickup
|
| lines | array<object> | no | |
| lines[].item_id | integer | sí | |
| lines[].quantity | number | sí | |
| lines[].uom_id | integer | no |
Respuestas
- 200 Partidas y totales (sin envolver)
curl -X POST https://demo.nori.software/api/v1/route/visits/42/quote \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"kind": "sale",
"lines": [
{
"item_id": 42,
"quantity": 1.5,
"uom_id": 42
}
]
}'
{
"lines": [
{}
],
"totals": {
"subtotal": "232.000000",
"discount_total": "…",
"tax_total": "…",
"total": "232.000000"
},
"errors": {}
}
Venta desde el camión
POST
/route/visits/{visit}/sale
Idempotency-Key
Venta desde el camión (remisión o factura), de contado o a crédito.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| visit | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| lines | array<object> | sí | |
| lines[].item_id | integer | sí | |
| lines[].quantity | number | sí | |
| lines[].uom_id | integer | no | |
| tenders | array<object> | no | |
| tenders[].tender_type_id | integer | sí | |
| tenders[].amount | number | sí | |
| tenders[].reference | string | no | |
| tenders[].credit_memo_id | integer | no |
Respuestas
- 200 Documentos y pago hechos en la parada (sin envolver)
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/route/visits/42/sale \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"lines": [
{
"item_id": 5,
"quantity": 2
}
],
"tenders": [
{
"tender_type_id": 1,
"amount": 46.4
}
]
}'
{
"message": "Listo.",
"documents": [
{
"id": 42,
"type": "…",
"folio": "A-1043",
"total": "232.000000",
"balance": "232.000000",
"print_url": "https://demo.nori.software/…"
}
],
"payment": {},
"visit": {},
"replayed": false
}
Pedido de preventa
POST
/route/visits/{visit}/order
Idempotency-Key
Pedido de preventa que se entrega en la siguiente visita.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| visit | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| lines | array<object> | sí | |
| lines[].item_id | integer | sí | |
| lines[].quantity | number | sí | |
| lines[].uom_id | integer | no | |
| delivery_date | string<date> | no |
Respuestas
- 200 Documentos y pago hechos en la parada (sin envolver)
curl -X POST https://demo.nori.software/api/v1/route/visits/42/order \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"lines": [
{
"item_id": 42,
"quantity": 1.5,
"uom_id": 42
}
],
"delivery_date": "2026-11-03"
}'
{
"message": "Listo.",
"documents": [
{
"id": 42,
"type": "…",
"folio": "A-1043",
"total": "232.000000",
"balance": "232.000000",
"print_url": "https://demo.nori.software/…"
}
],
"payment": {},
"visit": {},
"replayed": false
}
Entregar un pedido
POST
/route/visits/{visit}/delivery
Idempotency-Key
Entrega un pedido desde el camión (se permite parcial) y, si se quiere, cóbralo.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| visit | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| lines | array<object> | sí | |
| lines[].base_line_id | integer | no | |
| lines[].quantity | number | no | |
| tenders | array<object> | no | |
| tenders[].tender_type_id | integer | sí | |
| tenders[].amount | number | sí | |
| tenders[].reference | string | no | |
| tenders[].credit_memo_id | integer | no |
Respuestas
- 200 Documentos y pago hechos en la parada (sin envolver)
curl -X POST https://demo.nori.software/api/v1/route/visits/42/delivery \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"lines": [
{
"base_line_id": 42,
"quantity": 1.5
}
],
"tenders": [
{
"tender_type_id": 42,
"amount": 1.5,
"reference": "A-1043",
"credit_memo_id": 42
}
]
}'
{
"message": "Listo.",
"documents": [
{
"id": 42,
"type": "…",
"folio": "A-1043",
"total": "232.000000",
"balance": "232.000000",
"print_url": "https://demo.nori.software/…"
}
],
"payment": {},
"visit": {},
"replayed": false
}
Recolectar producto
POST
/route/visits/{visit}/pickup
Idempotency-Key
Recolecta productos (caducados, calidad) al compartimento de devoluciones.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| visit | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| lines | array<object> | sí | |
| lines[].item_id | integer | no | |
| lines[].quantity | number | no | |
| lines[].reason_id | integer | no |
Respuestas
- 200 Documentos y pago hechos en la parada (sin envolver)
curl -X POST https://demo.nori.software/api/v1/route/visits/42/pickup \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"lines": [
{
"item_id": 42,
"quantity": 1.5,
"reason_id": 42
}
]
}'
{
"message": "Listo.",
"documents": [
{
"id": 42,
"type": "…",
"folio": "A-1043",
"total": "232.000000",
"balance": "232.000000",
"print_url": "https://demo.nori.software/…"
}
],
"payment": {},
"visit": {},
"replayed": false
}
Cobrar en la parada
POST
/route/visits/{visit}/collection
Idempotency-Key
Cobra los documentos elegidos o los más antiguos.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| visit | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| tenders | array<object> | sí | |
| tenders[].tender_type_id | integer | sí | |
| tenders[].amount | number | sí | |
| tenders[].reference | string | no | |
| tenders[].credit_memo_id | integer | no | |
| document_ids | array<integer> | no |
Respuestas
- 200 Documentos y pago hechos en la parada (sin envolver)
curl -X POST https://demo.nori.software/api/v1/route/visits/42/collection \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"tenders": [
{
"tender_type_id": 42,
"amount": 1.5,
"reference": "A-1043",
"credit_memo_id": 42
}
],
"document_ids": [
42
]
}'
{
"message": "Listo.",
"documents": [
{
"id": 42,
"type": "…",
"folio": "A-1043",
"total": "232.000000",
"balance": "232.000000",
"print_url": "https://demo.nori.software/…"
}
],
"payment": {},
"visit": {},
"replayed": false
}
Hand over a home delivery scheduled on the stop (JSON, or multipart with the photo)
POST
/route/visits/{visit}/shipments/{shipment}/delivered
Idempotency-Key
Cash on delivery with a balance needs tenders; they are collected through the route's collection first. A reserve invoice shipped whole is delivered (stock moves) at this point.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| visit | integer | sí | |
| shipment | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Cuerpo de la petición · multipart/form-data
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| received_by | string | sí | |
| notes | string | no | |
| lat | number | no | |
| lng | number | no | |
| photo | string<binary> | no | |
| tenders | array<object> | no | |
| tenders[].tender_type_id | integer | sí | |
| tenders[].amount | number | sí | |
| tenders[].reference | string | no | |
| tenders[].credit_memo_id | integer | no |
Respuestas
- 200 Visita (sin envolver)
curl -X POST https://demo.nori.software/api/v1/route/visits/42/shipments/42/delivered \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-F "received_by=…" \
-F "notes=…" \
-F "lat=…" \
-F "lng=…" \
-F "photo=@foto.jpg" \
-F "tenders=…" \
-F "tenders[].tender_type_id=…" \
-F "tenders[].amount=…" \
-F "tenders[].reference=…" \
-F "tenders[].credit_memo_id=…"
{
"visit": {},
"replayed": false
}
A home delivery that could not be handed over, and why (back to the logistics board)
POST
/route/visits/{visit}/shipments/{shipment}/failed
Idempotency-Key
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| visit | integer | sí | |
| shipment | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| reason | string | sí | |
| notes | string | no |
Respuestas
- 200 Visita (sin envolver)
curl -X POST https://demo.nori.software/api/v1/route/visits/42/shipments/42/failed \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"reason": "…",
"notes": "…"
}'
{
"visit": {},
"replayed": false
}
Servicio a equipo
POST
/route/visits/{visit}/asset-service
Idempotency-Key
Servicio a refrigerador o exhibidor con fotos (multipart).
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| visit | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Cuerpo de la petición · multipart/form-data
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| customer_asset_id | integer | sí | |
| kind | string | sí |
Valores: preventivecorrectiveinstallationremovalinspection
|
| condition | string | sí |
Valores: workingneeds_repairout_of_service
|
| temperature | number | no | |
| notes | string | no | |
| performed_at | string<date-time> | no | |
| photos[] | array<string<binary>> | no |
Respuestas
- 200 Visita (sin envolver)
curl -X POST https://demo.nori.software/api/v1/route/visits/42/asset-service \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-F "customer_asset_id=…" \
-F "kind=…" \
-F "condition=…" \
-F "temperature=…" \
-F "notes=…" \
-F "performed_at=…" \
-F "photos[]=@foto.jpg"
{
"visit": {},
"replayed": false
}
Foto de la visita
POST
/route/visits/{visit}/photos
Idempotency-Key
Foto de la visita (multipart).
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| visit | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | no | Opcional en los endpoints de ruta; también se acepta como el campo "key". |
Cuerpo de la petición · multipart/form-data
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| photo | string<binary> | sí | |
| kind | string | no | |
| caption | string | no | |
| customer_asset_id | integer | no |
Respuestas
- 200 Visita (sin envolver)
curl -X POST https://demo.nori.software/api/v1/route/visits/42/photos \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-F "photo=@foto.jpg" \
-F "kind=…" \
-F "caption=…" \
-F "customer_asset_id=…"
{
"visit": {},
"replayed": false
}
Enviar la cola de ruta
POST
/route/sync
Cola sin conexión de la venta en ruta (acciones por nombre; /sync/push es la general).
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| operations | array<object> | sí | |
| operations[].key | string<uuid> | sí | |
| operations[].action | string | sí |
Valores: startfinishcheck_incheck_outskipno_salesaleorderdeliverypickupcollectionshipment_deliveredshipment_failed
|
| operations[].visit_id | integer | no | |
| operations[].shipment_id | integer | no | shipment_delivered / shipment_failed (without photo) |
| operations[].plan_id | integer | no | |
| operations[].data | object | no |
Respuestas
- 200 Resultados
curl -X POST https://demo.nori.software/api/v1/route/sync \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"operations": [
{
"key": "3f2b8c1e-7a4d-4f0e-9b8a-1c2d3e4f5a6b",
"action": "start",
"visit_id": 42,
"shipment_id": 42,
"plan_id": 42,
"data": {}
}
]
}'
{
"results": [
{}
]
}
Referencia
Supervisor
Indicadores de ventas, cobranza y cartera del día y del mes, por vendedor y sucursal.
Indicadores de ventas y cobranza
GET
/kpis
Ventas del día y del mes contra el periodo anterior, cobranza, cuentas por cobrar, vencido, antigüedad de saldos, artículos más vendidos, por vendedor y sucursal. Las secciones que el usuario no puede ver llegan en null. Un vendedor sin approvals.grant solo ve sus propias ventas.
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| branch_id | integer | no | |
| sales_employee_id | integer | no | |
| top | integer | no | Por omisión: 10 |
Respuestas
- 200 Indicadores
curl https://demo.nori.software/api/v1/kpis \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"sales_today": {
"value": 500,
"previous": 320,
"change_percent": 56.3,
"format": "money"
},
"top_items": [
{
"label": "ROSA · Rosa roja",
"value": 500
}
]
},
"meta": {
"generated_at": "2026-11-03T10:15:00-06:00"
}
}
Referencia
Sincronización
Descarga los catálogos para trabajar sin conexión por cambios incrementales y envía la cola de operaciones pendientes.
Entidades para trabajar sin conexión
GET
/sync
Respuestas
- 200 Manifiesto
curl https://demo.nori.software/api/v1/sync \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"entities": [
"items"
],
"page_size": 1,
"tombstone_days": 1,
"server_time": "2026-11-03T10:15:00-06:00"
}
}
Cambios de un catálogo
GET
/sync/{entity}
Una página de cambios de un catálogo desde un token de sincronización. Sin updated_since (o con uno más antiguo que los registros de borrado conservados), meta.reset es true: vacía la tabla local y toma todo. Sigue meta.next_cursor mientras meta.has_more sea true; después guarda meta.sync_token para el siguiente delta. Aplica deleted (ids) antes que data (altas y cambios).
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| entity | string | sí |
Valores: itemsitem_pricesstockcustomersprice_liststender_typeswarehousescausalitiesvisit_reasons
|
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| updated_since | string<date-time> | no | El sync_token de la última sincronización completa |
| cursor | string | no | |
| limit | integer | no | Por omisión: 500 Máximo: 2000 |
| scope | string | no |
customers: solo los míos
Valores: allmine
|
| warehouse_ids[] | array<integer> | no | stock: estos almacenes |
Respuestas
- 200 Cambios
- 403 Sobre de error
- 404 Sobre de error
curl https://demo.nori.software/api/v1/sync/items \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"id": 5,
"code": "ROSA",
"name": "Rosa roja",
"is_active": true,
"updated_at": "2026-11-03T09:58:12-06:00"
}
],
"deleted": [
17
],
"meta": {
"entity": "items",
"reset": false,
"has_more": false,
"next_cursor": null,
"sync_token": "2026-11-03T10:00:00-06:00",
"server_time": "2026-11-03T10:00:00-06:00"
}
}
Enviar la cola sin conexión
POST
/sync/push
Envía la cola sin conexión (escrituras de esta API, en orden). Cada operación se ejecuta con su llave como Idempotency-Key. Si una falla, las demás continúan. Una cadena "$ref:<key>:<path>" en un cuerpo toma ese valor de una respuesta anterior.
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| operations | array<object> | sí | |
| operations[].key | string<uuid> | sí | |
| operations[].method | string | sí |
Valores: POSTPUTPATCHDELETE
|
| operations[].path | string | sí | |
| operations[].body | object | no |
Respuestas
- 200 Resultado por operación
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/sync/push \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"operations": [
{
"key": "6f1c2a9e-0d0b-4b8e-9a43-0c1f1f3c0001",
"method": "POST",
"path": "/api/v1/customers",
"body": {
"name": "Tienda nueva"
}
},
{
"key": "6f1c2a9e-0d0b-4b8e-9a43-0c1f1f3c0002",
"method": "POST",
"path": "/api/v1/documents",
"body": {
"type": "sales_order",
"business_partner_id": "$ref:6f1c2a9e-0d0b-4b8e-9a43-0c1f1f3c0001:data.id",
"lines": [
{
"item_id": 5,
"quantity": 1
}
]
}
}
]
}'
{
"data": [
{
"key": "3f2b8c1e-7a4d-4f0e-9b8a-1c2d3e4f5a6b",
"status": 1,
"ok": true,
"replayed": false,
"body": {}
}
],
"meta": {
"ok": 1,
"failed": 1,
"server_time": "…"
}
}
Referencia
Asistente
Haz preguntas al asistente de IA y consulta tus conversaciones y las preguntas sugeridas.
Preguntar al asistente
POST
/assistant/messages
Idempotency-Key
Pregunta al asistente de IA (sin conversation_id inicia una conversación nueva).
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| conversation_id | integer | no | |
| message | string | sí |
Respuestas
- 201 Respuesta
- 403 Sobre de error
curl -X POST https://demo.nori.software/api/v1/assistant/messages \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"message": "¿Cuánto vendí este mes?"
}'
{
"data": {
"conversation_id": 42,
"message": {
"id": 42,
"text": "Listo.",
"html": "…"
}
}
}
Mis conversaciones
GET
/assistant/conversations
Mis últimas 50 conversaciones.
Respuestas
- 200 Conversaciones
curl https://demo.nori.software/api/v1/assistant/conversations \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{}
]
}
Mensajes de una conversación
GET
/assistant/conversations/{conversation}/messages
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| conversation | integer | sí |
Respuestas
- 200 Mensajes
curl https://demo.nori.software/api/v1/assistant/conversations/42/messages \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{}
]
}
Preguntas sugeridas
GET
/assistant/templates
Respuestas
- 200 Plantillas
curl https://demo.nori.software/api/v1/assistant/templates \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{}
]
}
Referencia
CRM
Trabaja tus oportunidades: embudos y etapas con sus totales, alta y edición, cambio de etapa, ganadas y perdidas con motivo, actividades, seguimientos, tu agenda y los documentos ligados.
Embudos con sus etapas
GET
/crm/pipelines
Embudos activos, el predeterminado primero, cada uno con sus etapas en el orden del tablero. outcome indica si una etapa está abierta, es la de ganada o la de perdida.
Respuestas
- 200 Embudos
- 403 Sobre de error
curl https://demo.nori.software/api/v1/crm/pipelines \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"id": 1,
"name": "Ventas",
"is_default": true,
"idle_days": 14,
"stages": [
{
"id": 1,
"name": "Prospecto",
"color": "#64748b",
"probability": "10.00",
"outcome": "open",
"sort_order": 0,
"auto_on": null
},
{
"id": 4,
"name": "Cotización enviada",
"color": "#f59e0b",
"probability": "50.00",
"outcome": "open",
"sort_order": 3,
"auto_on": "quotation"
},
{
"id": 6,
"name": "Ganado",
"color": "#16a34a",
"probability": "100.00",
"outcome": "won",
"sort_order": 5,
"auto_on": null
},
{
"id": 7,
"name": "Perdido",
"color": "#dc2626",
"probability": "0.00",
"outcome": "lost",
"sort_order": 6,
"auto_on": null
}
]
}
]
}
Cantidad, monto y monto ponderado por etapa
GET
/crm/pipelines/{pipeline}/totals
Los mismos totales e indicadores que el tablero de la web, con sus filtros y lo que el usuario puede ver. Las etapas abiertas cuentan oportunidades abiertas; las de ganada y perdida, lo que se cerró este mes.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| pipeline | integer | sí |
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| scope | string | no |
mine: solo las oportunidades de las que el usuario es responsable; all requiere crm_deals.view_all
Valores: mineall
|
| search | string | no | |
| tag_id | integer | no | |
| partner_id | integer | no | |
| overdue | boolean | no | Solo oportunidades con un seguimiento vencido |
Respuestas
- 200 Totales por etapa e indicadores
- 403 Sobre de error
- 404 Sobre de error
curl https://demo.nori.software/api/v1/crm/pipelines/42/totals?search=manzana \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"pipeline_id": 1,
"stages": [
{
"stage_id": 1,
"count": 4,
"amount": "18500.00",
"weighted_amount": "1850.00"
},
{
"stage_id": 4,
"count": 2,
"amount": "32000.00",
"weighted_amount": "16000.00"
}
],
"kpis": {
"open_count": 6,
"open_amount": "50500.00",
"weighted_amount": "17850.00",
"won_count": 3,
"won_amount": "41200.00"
}
}
}
Motivos de pérdida activos
GET
/crm/loss-reasons
Respuestas
- 200 Motivos de pérdida
- 403 Sobre de error
curl https://demo.nori.software/api/v1/crm/loss-reasons \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"id": 1,
"name": "Precio"
},
{
"id": 2,
"name": "Competencia"
},
{
"id": 3,
"name": "Sin presupuesto"
}
]
}
Oportunidades que puede ver el usuario
GET
/crm/deals
Sin crm_deals.view_all, solo las del usuario (responsable o de su empleado de ventas). Para trabajar sin conexión, guarda meta.server_time y mándalo como updated_since la siguiente vez: recibes lo que cambió desde entonces (también las cerradas) y en meta.deleted los ids que se borraron.
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| scope | string | no |
mine: solo las oportunidades de las que el usuario es responsable; all requiere crm_deals.view_all (si no, 403). Por omisión: todo lo que el usuario puede ver
Valores: mineall
|
| status | string | no |
Por omisión: todos los estados
Valores: openwonlost
|
| pipeline_id | integer | no | |
| stage_id | integer | no | |
| partner_id | integer | no | |
| tag_id | integer | no | |
| owner_id | integer | no | Solo con crm_deals.view_all |
| overdue | boolean | no | Solo oportunidades con un seguimiento vencido |
| search | string | no | Título, nombre del cliente, nombre comercial o código |
| updated_since | string<date-time> | no | meta.server_time de la descarga anterior (inclusive) |
| per_page | integer | no | Por omisión: 50 Máximo: 200 |
| cursor | string | no | meta.next_cursor de la página anterior |
Respuestas
- 200 Oportunidades
- 403 Sobre de error
- 422 Sobre de error
curl https://demo.nori.software/api/v1/crm/deals?search=manzana&per_page=50 \
-H "Authorization: Bearer $NORI_TOKEN"
{
"links": {},
"meta": {
"server_time": "2026-11-03T10:15:00-06:00",
"deleted": [
1
]
},
"data": [
{
"id": 31,
"title": "Surtido de temporada",
"status": "open",
"pipeline_id": 1,
"stage_id": 4,
"stage": {
"id": 4,
"name": "Cotización enviada",
"color": "#f59e0b",
"probability": "50.00",
"outcome": "open"
},
"business_partner_id": 12,
"partner": {
"id": 12,
"code": "C000012",
"name": "ABARROTES LUPITA SA DE CV",
"trade_name": "Abarrotes Lupita",
"type": "customer",
"phone": "3312345678",
"mobile": "3398765432",
"email": "compras@lupita.mx",
"latitude": 20.67,
"longitude": -103.35
},
"owner": {
"id": 5,
"name": "Ana Torres"
},
"amount": "25000.000000",
"weighted_amount": "12500.00",
"currency": "MXN",
"expected_close_date": "2026-12-15",
"close_overdue": false,
"tags": [
{
"id": 1,
"name": "Distribuidor",
"color": "#2563eb"
}
],
"next_activity": {
"id": 88,
"type": "call",
"subject": "Confirmar pedido",
"due_at": "2026-11-04T10:00:00-06:00",
"overdue": false
},
"pending_count": 1,
"overdue_count": 0,
"idle": false,
"days_in_stage": 2,
"updated_at": "2026-11-02T17:40:11-06:00"
}
]
}
Crear una oportunidad
POST
/crm/deals
Idempotency-Key
Sin embudo va al predeterminado; sin etapa, a su primera etapa abierta. El título es el nombre del cliente si no se manda, y el responsable es el usuario (solo quien tiene crm_deals.view_all asigna una oportunidad a alguien más). Corren las automatizaciones de "oportunidad creada" igual que en la web.
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| business_partner_id | integer | no | Obligatorio al crear |
| title | string | no | Por omisión: el nombre del cliente |
| crm_pipeline_id | integer | no | Solo al crear. Por omisión: el embudo predeterminado |
| crm_stage_id | integer | no | Solo al crear, una etapa de ese embudo. Por omisión: su primera etapa abierta |
| partner_contact_id | integer | null | no | |
| owner_id | integer | null | no | Solo con crm_deals.view_all; si no, el usuario |
| amount | number | no | |
| currency_id | integer | no | |
| probability | number | null | no | 0 a 100; null = la de la etapa |
| expected_close_date | string<date> | null | no | |
| source | string | no | |
| description | string | no | |
| tag_ids | array<integer> | no | Reemplaza las etiquetas |
Respuestas
- 201 Creado
- 403 Sobre de error
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/crm/deals \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"business_partner_id": 12,
"title": "Surtido de temporada",
"amount": 25000,
"expected_close_date": "2026-12-15",
"tag_ids": [
1
]
}'
{
"data": {
"id": 42,
"title": "…",
"status": "open",
"pipeline_id": 42,
"stage_id": 42,
"stage": {
"id": 42,
"name": "Rosa roja",
"color": "…",
"probability": "…",
"outcome": "…"
},
"business_partner_id": 42,
"partner": {
"id": 42,
"code": "ROSA",
"name": "Abarrotes Don Pepe",
"trade_name": "…",
"type": "…",
"phone": "3312345678",
"mobile": "3312345678",
"email": "ana@example.com",
"latitude": 1.5,
"longitude": 1.5
},
"partner_contact_id": 42,
"owner": {
"id": 42,
"name": "Rosa roja"
},
"sales_employee_id": 42,
"amount": "232.000000",
"weighted_amount": "232.000000",
"currency": "MXN",
"currency_id": 42,
"probability": "…",
"effective_probability": "…",
"expected_close_date": "2026-11-03",
"close_overdue": false,
"source": "…",
"description": "…",
"tags": [
{
"id": 42,
"name": "Rosa roja",
"color": "#2563eb"
}
],
"next_activity": {
"id": 42,
"type": "…",
"subject": "…",
"due_at": "2026-11-03T10:15:00-06:00",
"overdue": false
},
"pending_count": 1,
"overdue_count": 1,
"idle": false,
"days_in_stage": 1,
"loss_reason": {
"id": 42,
"name": "Rosa roja"
},
"loss_notes": "…",
"closed_at": "2026-11-03T10:15:00-06:00",
"last_activity_at": "2026-11-03T10:15:00-06:00",
"created_at": "2026-11-03T10:15:00-06:00",
"updated_at": "2026-11-03T10:15:00-06:00",
"contact": {
"id": 42,
"name": "Rosa roja",
"position": "…",
"email": "ana@example.com",
"phone": "3312345678",
"mobile": "3312345678"
},
"pipeline": {
"id": 42,
"name": "Rosa roja",
"stages": [
{
"id": null,
"name": null,
"color": "#f59e0b",
"probability": "232.000000",
"outcome": "open",
"sort_order": null,
"auto_on": "quotation"
}
]
},
"documents": [
{
"id": 42,
"type": "quotation",
"status": "…",
"folio": "A-1043",
"document_date": "2026-11-03",
"total": "232.000000"
}
],
"timeline": {
"pending": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
],
"history": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
]
}
}
}
Oportunidad con sus etapas, documentos y línea de tiempo
GET
/crm/deals/{deal}
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| deal | integer | sí |
Respuestas
- 200 Oportunidad
- 403 Sobre de error
- 404 Sobre de error
curl https://demo.nori.software/api/v1/crm/deals/42 \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": {
"id": 42,
"title": "…",
"status": "open",
"pipeline_id": 42,
"stage_id": 42,
"stage": {
"id": 42,
"name": "Rosa roja",
"color": "…",
"probability": "…",
"outcome": "…"
},
"business_partner_id": 42,
"partner": {
"id": 42,
"code": "ROSA",
"name": "Abarrotes Don Pepe",
"trade_name": "…",
"type": "…",
"phone": "3312345678",
"mobile": "3312345678",
"email": "ana@example.com",
"latitude": 1.5,
"longitude": 1.5
},
"partner_contact_id": 42,
"owner": {
"id": 42,
"name": "Rosa roja"
},
"sales_employee_id": 42,
"amount": "232.000000",
"weighted_amount": "232.000000",
"currency": "MXN",
"currency_id": 42,
"probability": "…",
"effective_probability": "…",
"expected_close_date": "2026-11-03",
"close_overdue": false,
"source": "…",
"description": "…",
"tags": [
{
"id": 42,
"name": "Rosa roja",
"color": "#2563eb"
}
],
"next_activity": {
"id": 42,
"type": "…",
"subject": "…",
"due_at": "2026-11-03T10:15:00-06:00",
"overdue": false
},
"pending_count": 1,
"overdue_count": 1,
"idle": false,
"days_in_stage": 1,
"loss_reason": {
"id": 42,
"name": "Rosa roja"
},
"loss_notes": "…",
"closed_at": "2026-11-03T10:15:00-06:00",
"last_activity_at": "2026-11-03T10:15:00-06:00",
"created_at": "2026-11-03T10:15:00-06:00",
"updated_at": "2026-11-03T10:15:00-06:00",
"contact": {
"id": 42,
"name": "Rosa roja",
"position": "…",
"email": "ana@example.com",
"phone": "3312345678",
"mobile": "3312345678"
},
"pipeline": {
"id": 42,
"name": "Rosa roja",
"stages": [
{
"id": null,
"name": null,
"color": "#f59e0b",
"probability": "232.000000",
"outcome": "open",
"sort_order": null,
"auto_on": "quotation"
}
]
},
"documents": [
{
"id": 42,
"type": "quotation",
"status": "…",
"folio": "A-1043",
"document_date": "2026-11-03",
"total": "232.000000"
}
],
"timeline": {
"pending": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
],
"history": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
]
}
}
}
Editar una oportunidad
PATCH
/crm/deals/{deal}
Idempotency-Key
Solo cambian los campos enviados. tag_ids reemplaza las etiquetas. El embudo y la etapa cambian con /move.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| deal | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| business_partner_id | integer | no | Obligatorio al crear |
| title | string | no | Por omisión: el nombre del cliente |
| crm_pipeline_id | integer | no | Solo al crear. Por omisión: el embudo predeterminado |
| crm_stage_id | integer | no | Solo al crear, una etapa de ese embudo. Por omisión: su primera etapa abierta |
| partner_contact_id | integer | null | no | |
| owner_id | integer | null | no | Solo con crm_deals.view_all; si no, el usuario |
| amount | number | no | |
| currency_id | integer | no | |
| probability | number | null | no | 0 a 100; null = la de la etapa |
| expected_close_date | string<date> | null | no | |
| source | string | no | |
| description | string | no | |
| tag_ids | array<integer> | no | Reemplaza las etiquetas |
Respuestas
- 200 Actualizado
- 403 Sobre de error
- 422 Sobre de error
curl -X PATCH https://demo.nori.software/api/v1/crm/deals/42 \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount": 31000,
"expected_close_date": "2026-12-20",
"partner_contact_id": 7
}'
{
"data": {
"id": 42,
"title": "…",
"status": "open",
"pipeline_id": 42,
"stage_id": 42,
"stage": {
"id": 42,
"name": "Rosa roja",
"color": "…",
"probability": "…",
"outcome": "…"
},
"business_partner_id": 42,
"partner": {
"id": 42,
"code": "ROSA",
"name": "Abarrotes Don Pepe",
"trade_name": "…",
"type": "…",
"phone": "3312345678",
"mobile": "3312345678",
"email": "ana@example.com",
"latitude": 1.5,
"longitude": 1.5
},
"partner_contact_id": 42,
"owner": {
"id": 42,
"name": "Rosa roja"
},
"sales_employee_id": 42,
"amount": "232.000000",
"weighted_amount": "232.000000",
"currency": "MXN",
"currency_id": 42,
"probability": "…",
"effective_probability": "…",
"expected_close_date": "2026-11-03",
"close_overdue": false,
"source": "…",
"description": "…",
"tags": [
{
"id": 42,
"name": "Rosa roja",
"color": "#2563eb"
}
],
"next_activity": {
"id": 42,
"type": "…",
"subject": "…",
"due_at": "2026-11-03T10:15:00-06:00",
"overdue": false
},
"pending_count": 1,
"overdue_count": 1,
"idle": false,
"days_in_stage": 1,
"loss_reason": {
"id": 42,
"name": "Rosa roja"
},
"loss_notes": "…",
"closed_at": "2026-11-03T10:15:00-06:00",
"last_activity_at": "2026-11-03T10:15:00-06:00",
"created_at": "2026-11-03T10:15:00-06:00",
"updated_at": "2026-11-03T10:15:00-06:00",
"contact": {
"id": 42,
"name": "Rosa roja",
"position": "…",
"email": "ana@example.com",
"phone": "3312345678",
"mobile": "3312345678"
},
"pipeline": {
"id": 42,
"name": "Rosa roja",
"stages": [
{
"id": null,
"name": null,
"color": "#f59e0b",
"probability": "232.000000",
"outcome": "open",
"sort_order": null,
"auto_on": "quotation"
}
]
},
"documents": [
{
"id": 42,
"type": "quotation",
"status": "…",
"folio": "A-1043",
"document_date": "2026-11-03",
"total": "232.000000"
}
],
"timeline": {
"pending": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
],
"history": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
]
}
}
}
Mover una oportunidad de etapa
POST
/crm/deals/{deal}/move
Idempotency-Key
A cualquier etapa, de su embudo o de otro. Una etapa de ganada o perdida cierra la oportunidad (perdida, con su motivo); una etapa abierta la reabre. El cambio queda en la línea de tiempo y dispara las automatizaciones.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| deal | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| stage_id | integer | sí | |
| comment | string | no | Se escribe con el cambio de etapa en la línea de tiempo |
| loss_reason_id | integer | no | Cuando la etapa es la de perdida |
| loss_notes | string | no |
Respuestas
- 200 Movida
- 403 Sobre de error
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/crm/deals/42/move \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"stage_id": 5,
"comment": "Pidió precio especial por volumen"
}'
{
"data": {
"id": 42,
"title": "…",
"status": "open",
"pipeline_id": 42,
"stage_id": 42,
"stage": {
"id": 42,
"name": "Rosa roja",
"color": "…",
"probability": "…",
"outcome": "…"
},
"business_partner_id": 42,
"partner": {
"id": 42,
"code": "ROSA",
"name": "Abarrotes Don Pepe",
"trade_name": "…",
"type": "…",
"phone": "3312345678",
"mobile": "3312345678",
"email": "ana@example.com",
"latitude": 1.5,
"longitude": 1.5
},
"partner_contact_id": 42,
"owner": {
"id": 42,
"name": "Rosa roja"
},
"sales_employee_id": 42,
"amount": "232.000000",
"weighted_amount": "232.000000",
"currency": "MXN",
"currency_id": 42,
"probability": "…",
"effective_probability": "…",
"expected_close_date": "2026-11-03",
"close_overdue": false,
"source": "…",
"description": "…",
"tags": [
{
"id": 42,
"name": "Rosa roja",
"color": "#2563eb"
}
],
"next_activity": {
"id": 42,
"type": "…",
"subject": "…",
"due_at": "2026-11-03T10:15:00-06:00",
"overdue": false
},
"pending_count": 1,
"overdue_count": 1,
"idle": false,
"days_in_stage": 1,
"loss_reason": {
"id": 42,
"name": "Rosa roja"
},
"loss_notes": "…",
"closed_at": "2026-11-03T10:15:00-06:00",
"last_activity_at": "2026-11-03T10:15:00-06:00",
"created_at": "2026-11-03T10:15:00-06:00",
"updated_at": "2026-11-03T10:15:00-06:00",
"contact": {
"id": 42,
"name": "Rosa roja",
"position": "…",
"email": "ana@example.com",
"phone": "3312345678",
"mobile": "3312345678"
},
"pipeline": {
"id": 42,
"name": "Rosa roja",
"stages": [
{
"id": null,
"name": null,
"color": "#f59e0b",
"probability": "232.000000",
"outcome": "open",
"sort_order": null,
"auto_on": "quotation"
}
]
},
"documents": [
{
"id": 42,
"type": "quotation",
"status": "…",
"folio": "A-1043",
"document_date": "2026-11-03",
"total": "232.000000"
}
],
"timeline": {
"pending": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
],
"history": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
]
}
}
}
Marcar la oportunidad como ganada
POST
/crm/deals/{deal}/win
Idempotency-Key
La mueve a la etapa de ganada de su embudo.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| deal | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Respuestas
- 200 Ganada
- 403 Sobre de error
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/crm/deals/42/win \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
{
"data": {
"id": 42,
"title": "…",
"status": "open",
"pipeline_id": 42,
"stage_id": 42,
"stage": {
"id": 42,
"name": "Rosa roja",
"color": "…",
"probability": "…",
"outcome": "…"
},
"business_partner_id": 42,
"partner": {
"id": 42,
"code": "ROSA",
"name": "Abarrotes Don Pepe",
"trade_name": "…",
"type": "…",
"phone": "3312345678",
"mobile": "3312345678",
"email": "ana@example.com",
"latitude": 1.5,
"longitude": 1.5
},
"partner_contact_id": 42,
"owner": {
"id": 42,
"name": "Rosa roja"
},
"sales_employee_id": 42,
"amount": "232.000000",
"weighted_amount": "232.000000",
"currency": "MXN",
"currency_id": 42,
"probability": "…",
"effective_probability": "…",
"expected_close_date": "2026-11-03",
"close_overdue": false,
"source": "…",
"description": "…",
"tags": [
{
"id": 42,
"name": "Rosa roja",
"color": "#2563eb"
}
],
"next_activity": {
"id": 42,
"type": "…",
"subject": "…",
"due_at": "2026-11-03T10:15:00-06:00",
"overdue": false
},
"pending_count": 1,
"overdue_count": 1,
"idle": false,
"days_in_stage": 1,
"loss_reason": {
"id": 42,
"name": "Rosa roja"
},
"loss_notes": "…",
"closed_at": "2026-11-03T10:15:00-06:00",
"last_activity_at": "2026-11-03T10:15:00-06:00",
"created_at": "2026-11-03T10:15:00-06:00",
"updated_at": "2026-11-03T10:15:00-06:00",
"contact": {
"id": 42,
"name": "Rosa roja",
"position": "…",
"email": "ana@example.com",
"phone": "3312345678",
"mobile": "3312345678"
},
"pipeline": {
"id": 42,
"name": "Rosa roja",
"stages": [
{
"id": null,
"name": null,
"color": "#f59e0b",
"probability": "232.000000",
"outcome": "open",
"sort_order": null,
"auto_on": "quotation"
}
]
},
"documents": [
{
"id": 42,
"type": "quotation",
"status": "…",
"folio": "A-1043",
"document_date": "2026-11-03",
"total": "232.000000"
}
],
"timeline": {
"pending": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
],
"history": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
]
}
}
}
Marcar la oportunidad como perdida, con el motivo
POST
/crm/deals/{deal}/lose
Idempotency-Key
La mueve a la etapa de perdida de su embudo. El motivo es obligatorio salvo que las notas lo expliquen.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| deal | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| loss_reason_id | integer | no | |
| loss_notes | string | no |
Respuestas
- 200 Perdida
- 403 Sobre de error
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/crm/deals/42/lose \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"loss_reason_id": 2,
"loss_notes": "Compró con la competencia a menor precio"
}'
{
"data": {
"id": 42,
"title": "…",
"status": "open",
"pipeline_id": 42,
"stage_id": 42,
"stage": {
"id": 42,
"name": "Rosa roja",
"color": "…",
"probability": "…",
"outcome": "…"
},
"business_partner_id": 42,
"partner": {
"id": 42,
"code": "ROSA",
"name": "Abarrotes Don Pepe",
"trade_name": "…",
"type": "…",
"phone": "3312345678",
"mobile": "3312345678",
"email": "ana@example.com",
"latitude": 1.5,
"longitude": 1.5
},
"partner_contact_id": 42,
"owner": {
"id": 42,
"name": "Rosa roja"
},
"sales_employee_id": 42,
"amount": "232.000000",
"weighted_amount": "232.000000",
"currency": "MXN",
"currency_id": 42,
"probability": "…",
"effective_probability": "…",
"expected_close_date": "2026-11-03",
"close_overdue": false,
"source": "…",
"description": "…",
"tags": [
{
"id": 42,
"name": "Rosa roja",
"color": "#2563eb"
}
],
"next_activity": {
"id": 42,
"type": "…",
"subject": "…",
"due_at": "2026-11-03T10:15:00-06:00",
"overdue": false
},
"pending_count": 1,
"overdue_count": 1,
"idle": false,
"days_in_stage": 1,
"loss_reason": {
"id": 42,
"name": "Rosa roja"
},
"loss_notes": "…",
"closed_at": "2026-11-03T10:15:00-06:00",
"last_activity_at": "2026-11-03T10:15:00-06:00",
"created_at": "2026-11-03T10:15:00-06:00",
"updated_at": "2026-11-03T10:15:00-06:00",
"contact": {
"id": 42,
"name": "Rosa roja",
"position": "…",
"email": "ana@example.com",
"phone": "3312345678",
"mobile": "3312345678"
},
"pipeline": {
"id": 42,
"name": "Rosa roja",
"stages": [
{
"id": null,
"name": null,
"color": "#f59e0b",
"probability": "232.000000",
"outcome": "open",
"sort_order": null,
"auto_on": "quotation"
}
]
},
"documents": [
{
"id": 42,
"type": "quotation",
"status": "…",
"folio": "A-1043",
"document_date": "2026-11-03",
"total": "232.000000"
}
],
"timeline": {
"pending": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
],
"history": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
]
}
}
}
Reabrir una oportunidad ganada o perdida
POST
/crm/deals/{deal}/reopen
Idempotency-Key
Regresa a la primera etapa abierta de su embudo.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| deal | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Respuestas
- 200 Reabierta
- 403 Sobre de error
curl -X POST https://demo.nori.software/api/v1/crm/deals/42/reopen \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
{
"data": {
"id": 42,
"title": "…",
"status": "open",
"pipeline_id": 42,
"stage_id": 42,
"stage": {
"id": 42,
"name": "Rosa roja",
"color": "…",
"probability": "…",
"outcome": "…"
},
"business_partner_id": 42,
"partner": {
"id": 42,
"code": "ROSA",
"name": "Abarrotes Don Pepe",
"trade_name": "…",
"type": "…",
"phone": "3312345678",
"mobile": "3312345678",
"email": "ana@example.com",
"latitude": 1.5,
"longitude": 1.5
},
"partner_contact_id": 42,
"owner": {
"id": 42,
"name": "Rosa roja"
},
"sales_employee_id": 42,
"amount": "232.000000",
"weighted_amount": "232.000000",
"currency": "MXN",
"currency_id": 42,
"probability": "…",
"effective_probability": "…",
"expected_close_date": "2026-11-03",
"close_overdue": false,
"source": "…",
"description": "…",
"tags": [
{
"id": 42,
"name": "Rosa roja",
"color": "#2563eb"
}
],
"next_activity": {
"id": 42,
"type": "…",
"subject": "…",
"due_at": "2026-11-03T10:15:00-06:00",
"overdue": false
},
"pending_count": 1,
"overdue_count": 1,
"idle": false,
"days_in_stage": 1,
"loss_reason": {
"id": 42,
"name": "Rosa roja"
},
"loss_notes": "…",
"closed_at": "2026-11-03T10:15:00-06:00",
"last_activity_at": "2026-11-03T10:15:00-06:00",
"created_at": "2026-11-03T10:15:00-06:00",
"updated_at": "2026-11-03T10:15:00-06:00",
"contact": {
"id": 42,
"name": "Rosa roja",
"position": "…",
"email": "ana@example.com",
"phone": "3312345678",
"mobile": "3312345678"
},
"pipeline": {
"id": 42,
"name": "Rosa roja",
"stages": [
{
"id": null,
"name": null,
"color": "#f59e0b",
"probability": "232.000000",
"outcome": "open",
"sort_order": null,
"auto_on": "quotation"
}
]
},
"documents": [
{
"id": 42,
"type": "quotation",
"status": "…",
"folio": "A-1043",
"document_date": "2026-11-03",
"total": "232.000000"
}
],
"timeline": {
"pending": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
],
"history": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
]
}
}
}
Ligar una cotización, pedido o factura del cliente
POST
/crm/deals/{deal}/documents
Idempotency-Key
El documento debe ser del cliente de la oportunidad (si no, 422 business_rule). Un documento contabilizado mueve la oportunidad a la etapa marcada para él ("mover aquí cuando"), nunca hacia atrás. Para hacer la cotización desde la oportunidad, manda crm_deal_id en POST /documents.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| deal | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| document_id | integer | sí |
Respuestas
- 200 Ligado
- 403 Sobre de error
- 404 Sobre de error
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/crm/deals/42/documents \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"document_id": 845
}'
{
"data": {
"id": 42,
"title": "…",
"status": "open",
"pipeline_id": 42,
"stage_id": 42,
"stage": {
"id": 42,
"name": "Rosa roja",
"color": "…",
"probability": "…",
"outcome": "…"
},
"business_partner_id": 42,
"partner": {
"id": 42,
"code": "ROSA",
"name": "Abarrotes Don Pepe",
"trade_name": "…",
"type": "…",
"phone": "3312345678",
"mobile": "3312345678",
"email": "ana@example.com",
"latitude": 1.5,
"longitude": 1.5
},
"partner_contact_id": 42,
"owner": {
"id": 42,
"name": "Rosa roja"
},
"sales_employee_id": 42,
"amount": "232.000000",
"weighted_amount": "232.000000",
"currency": "MXN",
"currency_id": 42,
"probability": "…",
"effective_probability": "…",
"expected_close_date": "2026-11-03",
"close_overdue": false,
"source": "…",
"description": "…",
"tags": [
{
"id": 42,
"name": "Rosa roja",
"color": "#2563eb"
}
],
"next_activity": {
"id": 42,
"type": "…",
"subject": "…",
"due_at": "2026-11-03T10:15:00-06:00",
"overdue": false
},
"pending_count": 1,
"overdue_count": 1,
"idle": false,
"days_in_stage": 1,
"loss_reason": {
"id": 42,
"name": "Rosa roja"
},
"loss_notes": "…",
"closed_at": "2026-11-03T10:15:00-06:00",
"last_activity_at": "2026-11-03T10:15:00-06:00",
"created_at": "2026-11-03T10:15:00-06:00",
"updated_at": "2026-11-03T10:15:00-06:00",
"contact": {
"id": 42,
"name": "Rosa roja",
"position": "…",
"email": "ana@example.com",
"phone": "3312345678",
"mobile": "3312345678"
},
"pipeline": {
"id": 42,
"name": "Rosa roja",
"stages": [
{
"id": null,
"name": null,
"color": "#f59e0b",
"probability": "232.000000",
"outcome": "open",
"sort_order": null,
"auto_on": "quotation"
}
]
},
"documents": [
{
"id": 42,
"type": "quotation",
"status": "…",
"folio": "A-1043",
"document_date": "2026-11-03",
"total": "232.000000"
}
],
"timeline": {
"pending": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
],
"history": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
]
}
}
}
Desligar un documento de la oportunidad
DELETE
/crm/deals/{deal}/documents/{document}
Idempotency-Key
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| deal | integer | sí | |
| document | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Respuestas
- 200 Desligado
- 403 Sobre de error
- 404 Sobre de error
curl -X DELETE https://demo.nori.software/api/v1/crm/deals/42/documents/42 \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
{
"data": {
"id": 42,
"title": "…",
"status": "open",
"pipeline_id": 42,
"stage_id": 42,
"stage": {
"id": 42,
"name": "Rosa roja",
"color": "…",
"probability": "…",
"outcome": "…"
},
"business_partner_id": 42,
"partner": {
"id": 42,
"code": "ROSA",
"name": "Abarrotes Don Pepe",
"trade_name": "…",
"type": "…",
"phone": "3312345678",
"mobile": "3312345678",
"email": "ana@example.com",
"latitude": 1.5,
"longitude": 1.5
},
"partner_contact_id": 42,
"owner": {
"id": 42,
"name": "Rosa roja"
},
"sales_employee_id": 42,
"amount": "232.000000",
"weighted_amount": "232.000000",
"currency": "MXN",
"currency_id": 42,
"probability": "…",
"effective_probability": "…",
"expected_close_date": "2026-11-03",
"close_overdue": false,
"source": "…",
"description": "…",
"tags": [
{
"id": 42,
"name": "Rosa roja",
"color": "#2563eb"
}
],
"next_activity": {
"id": 42,
"type": "…",
"subject": "…",
"due_at": "2026-11-03T10:15:00-06:00",
"overdue": false
},
"pending_count": 1,
"overdue_count": 1,
"idle": false,
"days_in_stage": 1,
"loss_reason": {
"id": 42,
"name": "Rosa roja"
},
"loss_notes": "…",
"closed_at": "2026-11-03T10:15:00-06:00",
"last_activity_at": "2026-11-03T10:15:00-06:00",
"created_at": "2026-11-03T10:15:00-06:00",
"updated_at": "2026-11-03T10:15:00-06:00",
"contact": {
"id": 42,
"name": "Rosa roja",
"position": "…",
"email": "ana@example.com",
"phone": "3312345678",
"mobile": "3312345678"
},
"pipeline": {
"id": 42,
"name": "Rosa roja",
"stages": [
{
"id": null,
"name": null,
"color": "#f59e0b",
"probability": "232.000000",
"outcome": "open",
"sort_order": null,
"auto_on": "quotation"
}
]
},
"documents": [
{
"id": 42,
"type": "quotation",
"status": "…",
"folio": "A-1043",
"document_date": "2026-11-03",
"total": "232.000000"
}
],
"timeline": {
"pending": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
],
"history": [
{
"id": null,
"deal_id": null,
"business_partner_id": null,
"type": "note",
"subject": null,
"body": null,
"occurred_at": null,
"due_at": null,
"remind_at": null,
"completed_at": null,
"is_follow_up": null,
"pending": null,
"overdue": null,
"editable": null,
"assigned_to": null,
"created_by": null,
"document": null,
"updated_at": null
}
]
}
}
}
Registrar o programar una actividad
POST
/crm/deals/{deal}/activities
Idempotency-Key
Sin due_at se registra como hecha en ese momento (una llamada, una visita, una nota). Con due_at es un seguimiento, pendiente hasta completarlo y asignado al responsable de la oportunidad salvo que assigned_to diga otra cosa; remind_minutes pone el recordatorio esos minutos antes. El asunto es el nombre del tipo si no se manda.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| deal | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| type | string | sí |
Valores: notecallemailwhatsappmeetingvisittask
|
| subject | string | no | Por omisión: el nombre del tipo |
| body | string | no | |
| due_at | string<date-time> | no | La vuelve un seguimiento |
| remind_at | string<date-time> | no | |
| remind_minutes | integer | no | Recordatorio estos minutos antes de due_at |
| assigned_to | integer | no | |
| completed | boolean | no | Un seguimiento que ya se hizo |
Respuestas
- 201 Creado
- 403 Sobre de error
- 422 Sobre de error
curl -X POST https://demo.nori.software/api/v1/crm/deals/42/activities \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"type": "visit",
"body": "Le gustó el exhibidor; pide muestra de 3 sabores."
}'
{
"data": {
"id": 42,
"deal_id": 42,
"business_partner_id": 42,
"type": "note",
"subject": "…",
"body": "…",
"occurred_at": "2026-11-03T10:15:00-06:00",
"due_at": "2026-11-03T10:15:00-06:00",
"remind_at": "2026-11-03T10:15:00-06:00",
"completed_at": "2026-11-03T10:15:00-06:00",
"is_follow_up": false,
"pending": false,
"overdue": false,
"editable": false,
"assigned_to": {
"id": 42,
"name": "Rosa roja"
},
"created_by": {
"id": 42,
"name": "Rosa roja"
},
"document": {
"id": 42,
"type": "…",
"folio": "A-1043"
},
"updated_at": "2026-11-03T10:15:00-06:00"
}
}
Completar, reabrir, reprogramar o editar una actividad
PATCH
/crm/activities/{activity}
Idempotency-Key
complete (con un outcome opcional que se agrega a sus notas), reopen y reschedule (due_at) aplican a seguimientos; edit cambia el asunto y el texto de lo que registró una persona. Lo que escribe Nori (cambios de etapa, documentos, automatizaciones) no se edita.
Parámetros de ruta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| activity | integer | sí |
Encabezados
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| Idempotency-Key | string<uuid> | sí | UUID que genera la app cuando el usuario actúa y que se guarda con la operación en su cola. |
Cuerpo de la petición
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| action | string | sí |
Valores: completereopenrescheduleedit
|
| outcome | string | no | |
| due_at | string<date-time> | no | Obligatorio para reprogramar |
| subject | string | no | Obligatorio para editar |
| body | string | no |
Respuestas
- 200 Actualizado
- 403 Sobre de error
- 404 Sobre de error
- 422 Sobre de error
curl -X PATCH https://demo.nori.software/api/v1/crm/activities/42 \
-H "Authorization: Bearer $NORI_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"action": "complete",
"outcome": "Confirmó el pedido para el lunes."
}'
{
"data": {
"id": 42,
"deal_id": 42,
"business_partner_id": 42,
"type": "note",
"subject": "…",
"body": "…",
"occurred_at": "2026-11-03T10:15:00-06:00",
"due_at": "2026-11-03T10:15:00-06:00",
"remind_at": "2026-11-03T10:15:00-06:00",
"completed_at": "2026-11-03T10:15:00-06:00",
"is_follow_up": false,
"pending": false,
"overdue": false,
"editable": false,
"assigned_to": {
"id": 42,
"name": "Rosa roja"
},
"created_by": {
"id": 42,
"name": "Rosa roja"
},
"document": {
"id": 42,
"type": "…",
"folio": "A-1043"
},
"updated_at": "2026-11-03T10:15:00-06:00"
}
}
Mi agenda: seguimientos por hacer
GET
/crm/agenda
Seguimientos pendientes de oportunidades abiertas asignados al usuario, los más próximos primero, hasta days días adelante; cada uno con su bucket (overdue, today, upcoming) y su oportunidad y cliente. scope=all (con crm_deals.view_all) lista los de todos.
Parámetros de consulta
| Nombre | Tipo | RequeridoReq. | Descripción |
|---|---|---|---|
| scope | string | no |
Valores: mineall
|
| days | integer | no | Cuántos días adelante Por omisión: 14 Máximo: 90 |
Respuestas
- 200 Seguimientos y conteo por grupo
- 403 Sobre de error
curl https://demo.nori.software/api/v1/crm/agenda \
-H "Authorization: Bearer $NORI_TOKEN"
{
"data": [
{
"id": 42,
"deal_id": 42,
"business_partner_id": 42,
"type": "note",
"subject": "…",
"body": "…",
"occurred_at": "2026-11-03T10:15:00-06:00",
"due_at": "2026-11-03T10:15:00-06:00",
"remind_at": "2026-11-03T10:15:00-06:00",
"completed_at": "2026-11-03T10:15:00-06:00",
"is_follow_up": false,
"pending": false,
"overdue": false,
"editable": false,
"assigned_to": {
"id": 42,
"name": "Rosa roja"
},
"created_by": {
"id": 42,
"name": "Rosa roja"
},
"document": {
"id": 42,
"type": "…",
"folio": "A-1043"
},
"updated_at": "2026-11-03T10:15:00-06:00",
"bucket": "overdue",
"deal": {
"id": 42,
"title": "…",
"status": "…",
"partner": {
"id": null,
"name": null,
"phone": null
}
}
}
],
"meta": {
"counts": {
"overdue": 1,
"today": 1,
"upcoming": 1
},
"server_time": "2026-11-03T10:15:00-06:00"
}
}