Referencia para quien integra

NORTHLAV API v1

Todo lo que hace falta para conectar un sistema externo: autenticación, endpoints, errores, límites y lo que esta API deliberadamente no hace.

¿Qué es?

Una interfaz, no un segundo producto

Northlav API deja que un sistema externo —un CRM, la web del negocio, un programa propio— lea las conversaciones de WhatsApp y escriba en ellas, sin pasar por el panel. Es la misma plataforma vista desde fuera: lo que entra por aquí pasa por las mismas reglas que lo que entra por el panel.

Una clave, un negocio.

Una credencial pertenece a un solo negocio y nunca alcanza a otro. No existen claves multi-negocio: si administras varios, cada uno crea la suya desde su propio panel.

Autenticación

El dueño del negocio crea la clave en Ajustes → Northlav API y te la pasa. Tiene esta forma:

Authorization: Bearer nl_live_<key_id>_<secreto>

La clave se muestra una sola vez.

Northlav guarda solo una huella. Nadie —tampoco nosotros— puede volver a mostrarla. Si se pierde, se revoca y se crea otra.

Nunca la pongas en el navegador, ni en una app móvil, ni en un repositorio. Es una credencial de servidor: quien la tenga puede escribirles a los clientes del negocio.

Va siempre en la cabecera Authorization. Nunca en la URL: las rutas acaban en los registros del proveedor, en el historial del navegador y en la cabecera Referer que se manda al siguiente sitio.

Base: https://cloud.northlavstudio.com/api/v1

Endpoints

POST/v1/contactscontacts:write

Encuentra o crea un contacto de WhatsApp por teléfono.

Crear un contacto NO simula un mensaje entrante: no mueve el embudo, no avisa al equipo y no despierta al agente. Si el contacto ya existe, su nombre NO se sobrescribe.

GET/v1/conversations/{id}conversations:read

Devuelve el historial de una conversación y el estado del agente.

Devuelve los mensajes en el orden en que ocurrieron. Se pagina hacia atrás con `cursor`, que es el `occurred_at` del mensaje más antiguo de la página anterior.

POST/v1/messagesmessages:write

Envía un mensaje de texto por el WhatsApp del negocio.

Requiere Idempotency-Key. El envío ocurre DENTRO de la petición: cuando responde, ya salió o ya falló. 201 si salió, 409 si quedó guardado sin poder enviarse. No hay 202 ni cola que consultar después.

GET/v1/messages/{id}messages:read

Dice qué pasó con un mensaje: si salió y si llegó.

Son dos estados distintos, y hay que leerlos como tales.

PATCH/v1/conversations/{id}/agent-stateconversations:manage

Pausa o reactiva al agente en esa conversación.

Manda `expected_version` si automatizas: es lo que evita pisar una intervención que alguien acaba de hacer.

GET/v1/productsproducts:read

Lista los productos con sus variantes (talla, color, SKU) y existencias.

Se pagina con `cursor`, que es el `next_cursor` de la página anterior. También existe GET /v1/products/{id}.

POST/v1/productsproducts:write

Crea o actualiza un producto, reconocido por `reference` o, si no viene, por su nombre.

Requiere Idempotency-Key. Las variantes se reconocen por `sku` o por talla + color, y su `stock` FIJA las unidades. Llevar existencias respeta el cupo de productos del plan. Opcional: `wholesale: { price, min_quantity }` para el precio por mayor (menor que `price`, desde 2 unidades); `null` lo quita.

PATCH/v1/products/{id}products:write

Cambia solo los campos que mandes: nombre, precio, precio por mayor (`wholesale`), referencia, categoría, descripción o `active`.

No toca existencias: para eso está POST /v1/stock.

POST/v1/stockinventory:write

Cambia existencias de hasta 100 variantes: `set` fija las unidades, `adjust` suma o resta.

Requiere Idempotency-Key: un `adjust` reintentado no resta dos veces. Nunca deja una talla en negativo (`would_go_negative`). Cada línea responde por sí sola.

Un envío, de principio a fin

Primero resuelves el contacto; la respuesta trae el conversation_id que usarás después.

curl -X POST https://cloud.northlavstudio.com/api/v1/contacts \
  -H "Authorization: Bearer $NORTHLAV_API_KEY" \
  -H "Idempotency-Key: alta-cliente-2026-09-19-0001" \
  -H "Content-Type: application/json" \
  -d '{"phone":"+50662056819","name":"Ana Rodriguez"}'

# 201 Created
# {"contact":{"id":"…","phone":"50662056819","name":"Ana Rodriguez"},
#  "conversation_id":"…","created":true}
curl -X POST https://cloud.northlavstudio.com/api/v1/messages \
  -H "Authorization: Bearer $NORTHLAV_API_KEY" \
  -H "Idempotency-Key: aviso-pedido-8842" \
  -H "Content-Type: application/json" \
  -d '{"conversation_id":"…","type":"text","text":"Su pedido esta listo."}'

# 201 Created
# {"message":{"id":"…","status":"sent"}}

Si vas a escribir por tu cuenta en una conversación, pausa antes al agente para que no conteste encima:

curl -X PATCH https://cloud.northlavstudio.com/api/v1/conversations/<id>/agent-state \
  -H "Authorization: Bearer $NORTHLAV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"state":"paused","pause_minutes":20,"expected_version":0}'

# 200 OK
# {"agent_state":{"requested":"paused","effective":"paused",
#                 "reason":"human_takeover","version":1}}

Tu sistema de inventario mantiene al día lo que Mortis ofrece: una venta en tu web resta una unidad, y Mortis deja de ofrecerla cuando se acaba.

curl -X POST https://cloud.northlavstudio.com/api/v1/products \
  -H "Authorization: Bearer $NORTHLAV_API_KEY" \
  -H "Idempotency-Key: producto-J01-2026-10-06" \
  -H "Content-Type: application/json" \
  -d '{"reference":"J01","name":"Jean Recto","price":119000,
       "variants":[{"size":"10","color":"Negro","sku":"J01-10-NE","stock":4}]}'

# 201 Created
# {"product":{"id":"…","name":"Jean Recto","variants":[{"sku":"J01-10-NE","stock":4,…}]},
#  "created":true}

curl -X POST https://cloud.northlavstudio.com/api/v1/stock \
  -H "Authorization: Bearer $NORTHLAV_API_KEY" \
  -H "Idempotency-Key: venta-web-55120" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"sku":"J01-10-NE","adjust":-1,"note":"pedido web 55120"}]}'

# 200 OK
# {"results":[{"sku":"J01-10-NE","status":"updated","stock":3}]}

Idempotencia: léelo antes de reintentar

Los dos POST exigen la cabecera Idempotency-Key: de 16 a 128 caracteres, letras, números, . _ ~ -. La generas tú, una por operación.

Sirve para el caso que más duele: la petición sale, se corta la conexión y no sabes si llegó. Reintenta con la misma clave y recibirás la respuesta original, sin que el cliente reciba el mensaje dos veces.

Misma clave + cuerpo distinto = 409. No es un reintento, es un error tuyo, y devolverte la respuesta anterior sería confirmarte algo que no se hizo.

La clave vale 7 días. Pasado ese plazo no reintentes automáticamente: consulta el estado y decide.

Rotar la clave de API no rompe nada. La idempotencia va por integración, no por credencial.

Avisos a tu sistema (webhooks)

En Ajustes → Northlav API → Avisos a tu sistema registras una dirección https pública y eliges qué quieres recibir. Northlav te hace un POST con JSON: { event, created_at, data }.

Eventos: sale.paid (pago confirmado, con sus artículos), stock.out (una talla o color quedó en 0 por una venta), message.received (mensaje nuevo de un cliente) y ping (el botón «Probar»).

Cabeceras: X-Northlav-Event, X-Northlav-Delivery (único por aviso: úsalo para no procesarlo dos veces) y X-Northlav-Signature: t=<segundos>,v1=<hex>.

Verifica la firma antes de confiar en el aviso: calcula HMAC-SHA256 de <t>.<cuerpo crudo> con tu secreto (whsec_…, se muestra una sola vez al crearlo) y compáralo con v1. Rechaza un t de hace más de 5 minutos.

Responde 2xx en menos de 10 s. Si no, se reintenta a 1 min, 5 min, 30 min, 2 h y 6 h; después se abandona y queda a la vista en Ajustes. No se siguen redirecciones.

«Salió» y «llegó» no son lo mismo

GET /v1/messages/{id} devuelve dos campos, y hay que leerlos por separado.

status

Qué hicimos nosotros: queued, sending, sent, failed, cancelled, delivery_uncertain. Un mensaje entrante es received.

delivery_status

Qué pasó en el teléfono de la otra persona: unknown, sent, delivered, read, failed.

sent significa que el proveedor lo aceptó, no que haya llegado al teléfono. Un fallo de entrega posterior convive perfectamente con una aceptación previa. Y delivery_uncertain quiere decir exactamente eso: salió y no tenemos confirmación. No lo trates como un fallo ni como un éxito — vuelve a consultar.

Límites y errores

Ritmo de peticiones

60 peticiones por minuto por clave, contando todas. Y dentro de ese cupo, 10 envíos por minuto: un envío cuenta en los dos cubos, así que no se puede rodear el general enviando.

El de envíos es mucho más estrecho a propósito. Leer de más molesta a la base; escribir de más le manda cuarenta mensajes a una persona real, y quien bloquea un número por spam es Meta. Eso deja al negocio sin canal y no lo arregla Northlav.

Al pasarte recibes 429 con la cabecera Retry-After en segundos. Espera ese tiempo: reintentar antes solo alarga el bloqueo. El cupo es por clave, así que rotar la credencial no lo arrastra.

Cuerpo

16 KiB

Texto de un mensaje

4000 caracteres

Página de mensajes

30 por defecto, 100 máximo

Canales

Solo WhatsApp en la v1

Todos los errores tienen la misma forma:

{"error":{"code":"resource_not_found",
          "message":"Recurso no encontrado.",
          "request_id":"7f3c…"}}

Guarda el request_id: es lo único que permite encontrar esa petición concreta si hay que mirarla. También viaja en la cabecera X-Request-Id de toda respuesta.

400invalid_requestFalta un campo, el JSON no es válido, o mandaste un campo de autoridad.
401unauthorizedClave ausente, mal formada, revocada, caducada o de una integración apagada.
403forbiddenLa clave existe pero no tiene el ámbito que pide esa ruta.
404resource_not_foundEl recurso no existe, o es de otro negocio. Los dos casos dan lo mismo a propósito.
409conflictEl canal no está configurado, la versión no coincide, o el envío no llegó a salir.
409idempotency_conflictReutilizaste una Idempotency-Key con un cuerpo distinto.
413payload_too_largeEl cuerpo pasa de 16 KiB.
429rate_limitedTe pasaste del ritmo. La cabecera Retry-After dice cuántos segundos esperar.
503service_unavailableNo se pudo comprobar la credencial. NO es que tu clave sea mala: reinténtalo.

Formatos y versionado

Teléfonos

Mándalos en E.164, con el prefijo de país: +50662056819, +573001234567. Northlav no adivina el país: un número sin prefijo al que se le pone uno por defecto acaba escribiéndole a un desconocido del otro lado del mundo, así que se rechaza con invalid_phone.

⚠️ Lo que se devuelve NO lleva el «+». Internamente se guardan solo dígitos, así que +50662056819 vuelve como 50662056819. Si tu sistema compara cadenas, normaliza antes.

Fechas

ISO 8601 en UTC: 2026-09-19T23:42:31Z. El cursor de paginación es exactamente una de estas fechas; trátalo como opaco y devuélvelo tal cual.

Codificación

Content-Type: application/json, UTF-8. El tope de 4.000 caracteres se cuenta en caracteres, no en bytes: un emoji cuenta como uno.

Versión

La versión va en la ruta: /v1. Se pueden añadir campos nuevos a una respuesta sin avisar, así que ignora los que no conozcas en vez de fallar.

Los cambios incompatibles van bajo otra versión. No se rompe /v1 en silencio.

Webhooks: todavía no existen

Northlav no te avisa cuando pasa algo. Tienes que preguntar.

No hay ningún evento saliente: ni cuando el cliente responde, ni cuando un mensaje se entrega, ni cuando falla. Se dice aquí y no se esconde, porque planificar una integración creyendo que llegarán avisos y descubrirlo después cuesta rehacer el trabajo.

Mientras tanto, consulta. Para mensajes nuevos de un cliente, pide GET /v1/conversations/{id} con limit pequeño y quédate con el occurred_at del último que ya tenías. Para saber si algo tuyo llegó, GET /v1/messages/{id}.

Ajusta el ritmo al cupo de 60 peticiones por minuto: una consulta cada pocos segundos por conversación activa cabe de sobra, un bucle sin espera no.

Los webhooks firmados —con reintentos e identificador de evento para que puedas descartar duplicados— están en el diseño de la siguiente fase. Cuando existan se documentarán aquí, y no antes.

Lo que esta API no hace

No son funciones pendientes: son límites a propósito.

No administra nada

Ninguna clave puede crear otras claves, cambiar la configuración del negocio, tocar su equipo, ver su facturación ni leer las credenciales de su canal.

No alcanza a otro negocio

El negocio sale de la fila de la credencial, nunca del cuerpo de la petición. Mandar un identificador de negocio en el JSON devuelve 400, no se ignora en silencio.

No maneja dinero

Los cobros son de Northlav Payments y no se exponen aquí. Ver el documento de Payments.

No le habla al modelo

No puedes cambiar el prompt de Mortis, ni sus herramientas, ni hacerle generar una respuesta bajo demanda. La API manda mensajes del negocio; el agente sigue siendo del negocio.

No entrega archivos

El historial dice que hubo una imagen o un audio, no dónde está. Los adjuntos viven en almacenamiento privado y se borran a los 14 días.

No acepta plantillas todavía

Solo texto. Escribirle a alguien fuera de la ventana de 24 horas de WhatsApp exige una plantilla aprobada por Meta, y esa lista aún no se administra por aquí.

De qué responde cada parte

Lo que la API envía sale del número de WhatsApp del negocio y, para quien lo recibe, lo escribió el negocio. El contenido, a quién se le manda y con qué frecuencia son responsabilidad de quien integra y del negocio que le dio la clave, no de Northlav.

Eso incluye respetar las reglas de Meta sobre mensajería, no escribirle a quien no lo ha pedido y no usar el canal para envíos masivos no solicitados. Un número que Meta bloquea por spam deja al negocio sin canal, y eso no lo arregla Northlav.

Northlav puede suspender una integración que incumpla estas reglas. Ver también Uso Aceptable y el Acuerdo de Tratamiento de Datos.

Cómo se conecta y cómo se corta

El dueño del negocio entra en Ajustes → Northlav API, da de alta la integración, elige qué ámbitos tendrá y genera la clave. Solo el administrador de la cuenta puede hacerlo.

Puede revocar una clave, que deja de funcionar en la siguiente petición, o apagar la integración entera, que corta todas sus claves de golpe. Las dos cosas son inmediatas y no requieren avisar a nadie.

Northlav Studio · Northlav API v1. Esta página describe el comportamiento del producto; no sustituye a los Términos y Condiciones. Última actualización: 19 de septiembre de 2026.