Poné tu artefacto adentro de agentia
Tu bot, tu app o tu proceso siguen corriendo donde ya corren, en tu código y tu infraestructura. Lo único que agregás son dos llamadas HTTP: una pregunta si el cliente está habilitado, la otra cuenta lo que hiciste. Nosotros cobramos, damos de baja y le mostramos al cliente para qué sirve lo que paga.
Quién hace qué
Todo lo que se hablan agentia y un complemento pasa por los endpoints de esta página. No hay otro camino, y ningún complemento tiene un trato distinto: el mismo contrato para todos.
Chayana
- Es dueña del canal del cliente: su WhatsApp y los demás.
- Decide a quién le toca cada conversación: por anuncio, porque el agente la deriva o porque vos la devolvés.
- Cobra y te dice si estás habilitado.
- Guarda la conversación entera y pone topes.
- Firma todo lo que te manda.
Tu complemento
- Es dueño de su lógica y de su estado.
- Decide qué contestar y cuándo soltar la conversación.
- Comprueba la firma de lo que le llega.
- Respeta
puede_atendery reporta lo que hizo. - Pone su propio tope, además del nuestro.
Nosotros no ejecutamos tu complemento ni conocemos su lógica. Vos no tocás Meta ni guardás credenciales del cliente.
| Quién llama | Qué | Para qué |
|---|---|---|
| El complemento | POST /api/complementos/verificar | Si el cliente está al día y podés trabajar. |
| El complemento | POST /api/complementos/reportar | Qué hiciste, para que el cliente vea para qué paga. |
| El complemento | POST /api/complementos/conversar | Pedirle una respuesta al agente sin soltar la conversación. |
| El complemento | POST /api/complementos/enviar | Mandar un mensaje por el canal del cliente. |
| El complemento | POST /api/complementos/asignar | Devolverle la conversación al agente. |
| El complemento | POST /api/complementos/escribiendo | Renovar el «escribiendo…» mientras trabajás. |
| Chayana | aviso mensaje | Alguien escribió en una conversación que atendés vos. |
| Chayana | aviso derivacion | El agente te pasa una conversación. |
Las dos primeras son lo mínimo: con eso un complemento que tiene su propio canal ya está integrado. Los avisos son opcionales y te llegan sólo si nos das una dirección donde recibirlos.
La credencial
Cuando instalamos tu complemento para un cliente te damos un token que arranca con agc_. Es por instalación: el mismo complemento vendido a diez clientes son diez tokens distintos, y dar de baja a uno no toca a los demás.
Va en la cabecera Authorization de todas las llamadas. Guardalo como guardarías una clave de base de datos: en una variable de entorno, nunca en el repositorio. Si se filtra, avisanos y lo rotamos; el viejo deja de servir en el acto.
Ese token es además la clave con la que firmamos la respuesta, así que es lo único que compartimos y no viaja nunca en el cuerpo de nada.
Verificar
Preguntá al arrancar, y después sólo cuando la licencia que tenés esté por vencer. No en cada mensaje.
POST /api/complementos/verificar
curl -X POST https://www.chayana.co/api/complementos/verificar \
-H "Authorization: Bearer agc_tu_token" \
-H "content-type: application/json" \
-d '{"version":"1.0.0"}'El cuerpo es opcional. Si mandás version la registramos, y es lo que nos permite dejar de soportar versiones viejas sin romperle el día a nadie. Si no la mandás y el cliente tiene una versión mínima configurada, no te habilitamos.
Respuesta · 200
{
"codigo": 1,
"estado": "habilitado",
"descripcion": "Habilitado.",
"puede_atender": true,
"modo": "completo",
"mensaje_para_el_usuario": null,
"vence": "2026-08-28T04:31:24.991Z",
"emitida": "2026-08-27T04:31:24.991Z",
"complemento": { "clave": "cotizador_piletas", "nombre": "Cotizador" },
"cliente": { "nombre": "AquaFibra", "agente": "Jordan" },
"uso": { "sesiones_del_mes": 12, "sesiones_incluidas": null },
"reportar_en": "https://www.chayana.co/api/complementos/reportar",
"firma": "K7x..."
}Siempre devolvemos 200 cuando la credencial es válida, aunque el cliente no pueda atender. El resultado está en el cuerpo, no en el estado HTTP: muchos clientes de HTTP tiran excepción ante un 402 o un 403 antes de que llegues a leer el motivo y el mensaje para la persona. El 401 sí es un 401: significa que no te reconocemos.
| Campo | Tipo | Para qué |
|---|---|---|
| codigo | número | 1 a 5. Para el registro y para vos, no para ramificar. |
| estado | texto | Slug estable. Distingue dos situaciones con el mismo código. |
| descripcion | texto | En castellano, para tus logs. No se la muestres a nadie. |
| puede_atender | booleano | Sobre esto ramificás. Es la única pregunta que importa. |
| modo | texto | Hoy siempre "completo". Existe para que un modo nuevo no te obligue a redesplegar. |
| mensaje_para_el_usuario | texto o null | Qué decirle a la persona cuando no podés atender. Lo escribimos nosotros. |
| vence | fecha ISO | Hasta cuándo vale esta licencia. Guardala hasta entonces. |
| emitida | fecha ISO | Cuándo se emitió. Entra en la firma. |
| complemento | objeto | clave y nombre del complemento. |
| cliente | objeto | nombre del cliente y de su agente en agentia. |
| uso | objeto | sesiones_del_mes y sesiones_incluidas. Informativo: no cortamos por consumo. |
| reportar_en | URL | A dónde mandar el reporte. No la hardcodees. |
| firma | texto | HMAC de los campos de la decisión, con tu token. |
Los códigos
Están para tus registros y para que un humano entienda qué pasó. No ramifiques sobre ellos: para eso está puede_atender.
| Código | Qué es | ¿Atiende? | Cuándo |
|---|---|---|---|
| 1 | Habilitado | Sí | Todo en orden. |
| 2 | Falta de pago | Durante una semana | Vencido el pago hay siete días de gracia atendiendo. Después deja de atender. |
| 3 | Inactivo | No | Suspendido o dado de baja desde el panel, contrato vencido, o suscripción en pausa. |
| 4 | Sin conexión | Lo decidís vos | No lo devolvemos nunca. Lo generás vos cuando no nos alcanzás. |
| 5 | Otro | No | Credencial desconocida, o tu versión es anterior a la mínima soportada. |
Cuatro reglas
Las formas de los mensajes se entienden solas. Esto es lo que hay que hacer bien, y es todo.
Comprobar la firma
Firmamos una concatenación de seis campos, no el JSON entero: si firmáramos el cuerpo tal cual, los dos lados tendrían que ordenar las claves igual y eso se rompe solo. El separador es | y puede_atender entra como 1 o 0. El resultado es HMAC-SHA256 en base64url, con tu token como clave.
El mensaje que se firma
codigo|estado|puede_atender|modo|vence|emitida
1|habilitado|1|completo|2026-08-28T04:31:24.991Z|2026-08-27T04:31:24.991ZJavaScript
import { createHmac, timingSafeEqual } from "node:crypto";
function firmaValida(l, token) {
const mensaje = [
l.codigo, l.estado, l.puede_atender ? 1 : 0, l.modo, l.vence, l.emitida,
].join("|");
const esperada = createHmac("sha256", token).update(mensaje).digest("base64url");
const a = Buffer.from(esperada);
const b = Buffer.from(l.firma ?? "");
return a.length === b.length && timingSafeEqual(a, b);
}Python
import base64, hmac, hashlib
def firma_valida(l, token):
mensaje = "|".join([
str(l["codigo"]), l["estado"], "1" if l["puede_atender"] else "0",
l["modo"], l["vence"], l["emitida"],
])
esperada = base64.urlsafe_b64encode(
hmac.new(token.encode(), mensaje.encode(), hashlib.sha256).digest()
).rstrip(b"=").decode()
return hmac.compare_digest(esperada, l.get("firma", ""))Ojo con el base64url: va sin relleno. En Python hay que sacar los = del final, que es donde se tropieza todo el mundo.
Reportar
Una sesión es una charla, una visita o una corrida, según lo que seas. Nos decís a qué paso llegó y cómo terminó. Eso es todo lo que interpretamos; lo que pongas en detalle se guarda tal cual y no lo tocamos.
Reportá a qué paso llegó, no por cuáles pasó: una sola variable que se pisa. Quien llegó al cuarto paso pasó por los tres anteriores, y el embudo lo armamos nosotros. Los nombres de los pasos y de los desenlaces los acordamos al dar de alta tu complemento, y el orden en que los declarás es el orden del embudo.
POST /api/complementos/reportar
curl -X POST https://www.chayana.co/api/complementos/reportar \
-H "Authorization: Bearer agc_tu_token" \
-H "content-type: application/json" \
-d '{
"version": "1.0.0",
"sesiones": [
{
"id": "wa-5491122334455-1782",
"inicio": "2026-08-27T10:00:00Z",
"fin": "2026-08-27T10:06:31Z",
"paso": "precio",
"desenlace": "cotizado",
"contacto": "Marta",
"detalle": { "modelo": "8x4", "metros": 32 }
}
]
}'Respuesta · 200
{ "recibidas": 1, "guardadas": 1, "descartadas": 0 }Hasta 500 sesiones por lote. Podés mandarlas de a una al cerrar cada sesión, o juntar y mandar cada tanto: nos da igual. Una sesión sin fecha de inicio legible se descarta sola, sin tirar el lote entero, y te lo decimos en descartadas.
Una sesión sin fin se cuenta como abierta. Podés reportarla al empezar y volver a mandarla completa después, con el mismo id: la segunda pisa a la primera.
Pasarle una consulta al agente
Tu artefacto sabe hacer una cosa muy bien y se traba con todo lo demás. Cotiza una pileta sin equivocarse en la cuenta y no tiene idea de si la lona aguanta granizo. En vez de contestar cualquier cosa o cortar, le pasás la pregunta al agente del cliente, que sí tiene el conocimiento del negocio cargado.
Te devolvemos el texto y lo mandás vos: el dueño del canal seguís siendo vos. Mandá el mismo sesion_id que usás para reportar y la charla continúa donde iba, así que podés delegar un turno, tres, o el resto de la conversación.
POST /api/complementos/conversar
curl -X POST https://www.chayana.co/api/complementos/conversar \
-H "Authorization: Bearer agc_tu_token" \
-H "content-type: application/json" \
-d '{
"sesion_id": "5491122334455",
"mensaje": "¿la lona de la 8x4 aguanta granizo?",
"contacto": "Marta",
"conversacion": [
{ "quien": "persona", "texto": "Hola, quiero una pileta" },
{ "quien": "complemento", "texto": "¡Hola! ¿Qué modelo tenés en mente?" },
{ "quien": "persona", "texto": "La 8x4" },
{ "quien": "complemento", "texto": "Son 32 m2. Te queda en USD 4.200." }
],
"contexto": "Total cotizado 4200. Presupuesto interno #8812."
}'Respuesta · 200
{
"atendido": true,
"codigo": 1,
"estado": "atendido",
"respuesta": "Sí, la lona reforzada que usamos está probada para granizo...",
"conversacion_id": "8f3c...",
"costo_usd": 0.0142
}conversacion es la charla que venías teniendo, tal cual ocurrió de tu lado. Es lo que hace que el agente entre sabiendo: si la persona ya eligió, dio medidas y recibió un precio, tiene que leer eso y no un resumen de dos líneas. Se siembra una sola vez, en el primer traspaso de esa sesión, así que podés mandarla completa en cada llamada —que es lo más fácil de programar— sin que se duplique nada.
contexto es otra cosa: lo que sabés y no se dijo en la charla —un total, un id interno, el nombre del vendedor—. Va a las instrucciones del turno y no al historial, así que no le ensucia al cliente la conversación que después lee. Mandalo en cada llamada si cambia.
Cuando no se pudo, atendido viene en false con el motivo en estado: agente_no_operativo si el cliente todavía no puso su agente en producción, sin_comportamiento si no tiene uno publicado, el_motor_fallo si falló de nuestro lado. Y si el complemento no está habilitado, contesta lo mismo que la verificación, con mensaje_para_el_usuario listo para relevar. Sigue siendo 200: el motivo lo tenés que poder leer.
La charla queda del lado de agentia, en la misma pantalla de conversaciones que el resto y marcada como llegada por un complemento. Ese es el punto: el cliente tiene un solo lugar donde mirar. El consumo de tokens va a su cuenta, no a la tuya, porque es su agente el que trabaja.
Tarda lo que tarda un modelo en contestar, así que no la llames con un timeout de dos segundos. Y si te contesta que no atendió, seguí vos: es tu conversación.
Recibir una conversación
Es la vuelta, y es opcional. El agente del cliente puede devolverte una conversación —o pasarte una que empezó de su lado— cuando lo que la persona pide es justo lo que vos sabés hacer. Para eso necesitamos una dirección donde alcanzarte: se la das a Chayana al instalar y tiene que ser https.
Acá no te mandamos la charla: arrancás tu proceso desde donde corresponda. Si la conversación vino por un canal del cliente —su WhatsApp, por ejemplo— el campo canal lo dice y el hilo pasa a ser tuyo en ese momento: lo que la persona escriba después te llega como mensaje, igual que si hubiera entrado por un anuncio, y contestás con /enviar o soltás con /asignar. Si canal viene nulo, seguís como siempre por /conversar.
Lo que te llega, por POST
{
"evento": "derivacion",
"sesion_id": "wa-5491122334455-1782",
"conversacion_id": "8f3c...",
"contacto": "Marta",
"canal": "whatsapp",
"motivo": "Quiere cotizar una pileta",
"resumen": "Preguntó por modelos y precios de piletas de fibra.",
"emitida": "2026-08-27T21:14:02.318Z"
}El sesion_id te dice de quién se trata. Si la conversación había empezado de tu lado, es el id que ya usás para reportar y para consultarnos. Si empezó del nuestro, es el identificador de la persona en el canal por donde escribió: en WhatsApp, su número. No es un id para reportar. Cuando la derivación trae canal, la clave del hilo es conversacion_id: reportá y consultá esa sesión con un id tuyo derivado de él, el mismo en /reportar y en /conversar.
Viene firmado en la cabecera x-agentia-firma: es el HMAC-SHA256 del cuerpo entero, tal cual lo recibiste, con tu token como clave y en base64url. Comprobalo antes de hacer nada. Sin eso, cualquiera que descubra tu dirección puede empujarte conversaciones inventadas.
Comprobarlo
const firma = req.headers["x-agentia-firma"];
const esperada = createHmac("sha256", TOKEN)
.update(cuerpoCrudo) // el texto sin parsear, no el objeto
.digest("base64url");
if (firma !== esperada) return res.status(401).end();Contestá 2xx si la tomás. Si contestás un error, o si tardás más de ocho segundos, el agente se queda atendiendo él y le avisa a la persona: preferimos eso antes que dejarla sin nadie del otro lado.
Con canal, podés contestar la derivación con { "responder": "..." } para saludar: sale por el canal justo después del mensaje del agente que le avisa a la persona que la pasa. Es mejor que un /enviar inmediato, que puede llegar antes que ese aviso.
Atender por el WhatsApp del cliente
Hasta acá el dueño del canal seguías siendo vos: te devolvíamos texto y lo mandabas. Con esto podés no tener ninguno. El cliente ya conectó su WhatsApp en Chayana, y vos atendés por ahí sin hacer nada con Meta: ni número, ni webhook, ni verificación.
Sirve para el caso más común que hay: una persona toca un anuncio, se le abre WhatsApp, escribe, y la atendés vos.
Te llega el mensaje
A la misma dirección donde recibís las derivaciones, con la misma firma. Si la conversación entró por un anuncio del que te hacés cargo, la tomás vos y todo el hilo sigue viniendo acá hasta que la sueltes.
Lo que te llega, por POST
{
"evento": "mensaje",
"conversacion_id": "8f3c...",
"canal": "whatsapp",
"contacto": { "id": "5493875550000", "nombre": "Marta" },
"mensaje": "Hola, quiero cotizar una pileta",
"origen": {
"anuncioId": "120226305854810726",
"tipo": "ad",
"titular": "Cotizá tu pileta",
"clicId": "Aff-n8ZTODiE79d22KtAwQKj9e"
}
}origen viene sólo en el primer mensaje, que es el que dispara el anuncio. Los siguientes llegan sin eso, así que si lo necesitás, guardalo.
Contestás en el momento
{ "responder": "Dale, ¿de qué medida la querés?" }Lo mandamos nosotros por el número del cliente. Tenés 8 segundos: si vas a tardar más —cotizar, consultar un sistema— contestá { "recibido": true } y usá la llamada de abajo cuando tengas la respuesta.
Mandás vos
POST /api/complementos/enviar
curl -X POST https://www.chayana.co/api/complementos/enviar \
-H "Authorization: Bearer agc_tu_token" \
-H "content-type: application/json" \
-d '{
"conversacion_id": "8f3c...",
"mensaje": "Te queda USD 12.400, incluye instalación."
}'Sólo en conversaciones que tengas tomadas. El mensaje sale a nombre del negocio del cliente, así que sin esa comprobación tu token serviría para escribirle a cualquier contacto suyo. Hay tope por hora: un bucle mal escrito son miles de mensajes desde un número que no es tuyo, y al que le bajan la calificación es al cliente.
Se la pasás al agente
POST /api/complementos/asignar
curl -X POST https://www.chayana.co/api/complementos/asignar \
-H "Authorization: Bearer agc_tu_token" \
-H "content-type: application/json" \
-d '{
"conversacion_id": "8f3c...",
"contexto": "Cotizó 8x4, quiere hablar de financiación"
}'Desde ahí contesta el agente, con toda la charla adelante: lo que contestaste vos ya quedó guardado, no hace falta que mandes el historial. En contexto va lo que sabés y no está dicho en la charla.
Es una sola dirección. Después de esto, /enviar sobre esa conversación devuelve 409. Si el agente necesita volver a pasártela, tiene su propia herramienta para derivar.
Escribiendo…
Cada vez que te entregamos un mensaje, a la persona le mostramos que le están escribiendo. En WhatsApp además le aparece el mensaje como leído. Dura hasta que sale la respuesta o 25 segundos, lo que pase primero, y no tenés que hacer nada.
Si vas a tardar más —cotizar, armar un PDF—, renovalo cada tanto mientras trabajás. Así la persona no mira una pantalla quieta.
POST /api/complementos/escribiendo
{ "conversacion_id": "8f3c..." }
→ 200 { "escribiendo": true }Mismas reglas que /enviar: solo en conversaciones que atendés (409 si no), 403 si la licencia no te habilita. Si el canal no tiene indicador —el correo, por ejemplo—, contesta 200 con escribiendo: false: no es un error. Hay un tope de 600 por hora por instalación. No lo llames si no vas a contestar.
Botones, listas, documentos e imágenes
Además de texto, por el canal del cliente podés mandar opciones para tocar, un PDF y una imagen. Todo es opcional: si no lo usás, nada cambia.
Opciones, por /enviar o junto con responder
{
"conversacion_id": "8f3c...",
"mensaje": "¿Cómo podemos ayudarte?",
"opciones": {
"items": [
{ "id": "pileta", "titulo": "Quiero una pileta" },
{ "id": "ayuda", "titulo": "Necesito ayuda" }
]
}
}tipoesbotonesolista. Si falta, hasta 3 van como botones y de 4 a 10 como lista.- Botones: de 1 a 3, título hasta 20 caracteres, mensaje hasta 1024. No llevan descripción.
- Lista: de 1 a 10, título hasta 24, descripción hasta 72,
botonhasta 20 (por defecto "Ver opciones"), mensaje hasta 4096. - El
idva hasta 200 caracteres. Ni los ids ni los títulos se repiten. - Solo con botones, una imagen arriba:
"encabezado": { "imagen": "https://..." }. Con lista es un 400. Si la imagen no baja o el canal no la muestra, los botones salen sin ella; en/enviarte contestamos"encabezado": false.
No recortamos nada: dos títulos recortados pueden quedar iguales. En /enviar, lo que no cumple es un 400 que dice qué campo y por qué. En la respuesta de un aviso no tenés cómo enterarte de un error, así que ahí —y también si el canal no muestra botones o si WhatsApp los rechaza— mandamos el texto con las opciones numeradas desde 1, en el orden de items. La persona nunca se queda sin la pregunta.
Cuando la persona toca, te llega
{
"evento": "mensaje",
"conversacion_id": "8f3c...",
"mensaje": "Quiero una pileta",
"respuesta": { "id": "pileta", "titulo": "Quiero una pileta" }
}mensaje sigue trayendo el título, para el que no lee respuesta. respuesta es nulo cuando la persona escribió en vez de tocar, y si toca un botón de un mensaje viejo te llega igual con su id.
Un PDF, solo por /enviar
{
"conversacion_id": "8f3c...",
"mensaje": "Tu presupuesto AF-20260910-ABC123. Válido por 15 días.",
"documento": {
"url": "https://tu-servidor/presupuestos/AF-20260910-ABC123.pdf?firma=...",
"nombre": "Presupuesto AF-20260910-ABC123.pdf"
}
}Lo bajamos al recibir el pedido, lo validamos y lo guardamos: a WhatsApp le pasamos un link nuestro de corta vida, nunca el tuyo, y el cliente lo puede abrir desde su pantalla. La dirección tiene que ser https y no puede apuntar a una red privada; no seguimos redirecciones; tiene que responder en diez segundos, pesar hasta 5 MB, ser application/pdf y empezar con %PDF. El mensaje va como pie, hasta 1024. No va junto con opciones.
Una imagen, solo por /enviar
{
"conversacion_id": "8f3c...",
"mensaje": "Rectangular: la que mejor aprovecha patios angostos.",
"imagen": { "url": "https://tu-servidor/modelos/rectangular.png" }
}Las mismas reglas que el PDF, pero tiene que ser image/png o image/jpeg y empezar como tal. El mensaje va como pie, hasta 1024. No va junto con opciones ni con documento.
Si el documento o la imagen no se pudo, contestamos 422 con el motivo —direccion_no_permitida, no_se_pudo_bajar, no_es_pdf, no_es_imagen, muy_grande, el_canal_no_admite_documentos o el_canal_no_admite_imagenes— y no sale nada: mandá el texto solo.
Cada mensaje con opciones, documento o imagen cuenta como uno para el tope por hora. Lo que se mostró, lo que se eligió, el documento y la imagen quedan en la conversación: el cliente los ve en su pantalla, y el agente los tiene en su historial cuando le devolvés la charla.
Ejemplo completo
Esto es todo lo que hay que agregarle a un artefacto que ya funciona.
agentia.js
import { createHmac, timingSafeEqual } from "node:crypto";
const AGENTIA = "https://www.chayana.co";
const TOKEN = process.env.AGENTIA_TOKEN;
const VERSION = "1.0.0";
let licencia = null;
export async function licenciaVigente() {
// Mientras esté vigente no se pregunta nada. Esta línea es la que hace que
// una caída de agentia no sea una caída tuya.
if (licencia && new Date(licencia.vence) > new Date()) return licencia;
try {
const r = await fetch(AGENTIA + "/api/complementos/verificar", {
method: "POST",
headers: {
authorization: "Bearer " + TOKEN,
"content-type": "application/json",
},
body: JSON.stringify({ version: VERSION }),
});
if (r.status === 401) {
// Nuestra credencial no vale. Reintentar no la va a arreglar.
console.error("agentia: credencial rechazada");
return licencia;
}
const nueva = await r.json();
if (!firmaValida(nueva)) {
console.error("agentia: firma inválida, alguien está en el medio");
return licencia;
}
licencia = nueva;
} catch (error) {
// Código 4: no se pudo verificar. Con licencia vieja pero de hoy seguimos;
// sin nada, decidimos nosotros. Acá elegimos atender.
if (!licencia) {
return { codigo: 4, puede_atender: true, modo: "completo" };
}
}
return licencia;
}
/** Poner donde el artefacto está por contestar. */
export async function atender(responder) {
const l = await licenciaVigente();
if (!l.puede_atender) return responder(l.mensaje_para_el_usuario);
return null;
}
export async function reportar(sesiones) {
const l = await licenciaVigente();
const url = l.reportar_en ?? AGENTIA + "/api/complementos/reportar";
await fetch(url, {
method: "POST",
headers: {
authorization: "Bearer " + TOKEN,
"content-type": "application/json",
},
body: JSON.stringify({ version: VERSION, sesiones }),
});
}
function firmaValida(l) {
const mensaje = [
l.codigo, l.estado, l.puede_atender ? 1 : 0, l.modo, l.vence, l.emitida,
].join("|");
const esperada = createHmac("sha256", TOKEN).update(mensaje).digest("base64url");
const a = Buffer.from(esperada);
const b = Buffer.from(l.firma ?? "");
return a.length === b.length && timingSafeEqual(a, b);
}Cuando algo falla
| Qué pasó | Qué hacer |
|---|---|
| No nos alcanzás y tenés licencia vigente | Seguí trabajando. Ni la mires: para eso vence recién mañana. |
| No nos alcanzás y la licencia venció | Es tuya la decisión. Nuestra recomendación: seguí atendiendo y reintentá cada pocos minutos. Un cliente que paga no tiene por qué quedarse sin servicio por un problema nuestro. |
| Contestamos 401 | Tu credencial no existe o fue rotada. No reintentes en loop: avisá y pará. |
| La firma no da | Descartá la respuesta y quedate con la licencia anterior. Alguien está en el medio. |
| El reporte devuelve 400 | Hay un error en tu carga, no de conexión. El cuerpo trae el campo que falla. |
| El reporte devuelve 500 | Fue nuestro. Reintentá el mismo lote más tarde: con los mismos ids no duplica. |
OpenAPI
Si preferís generarte el cliente en vez de escribirlo, ahí está la especificación. Tené presente que describe las formas de los mensajes y no los comportamientos: lo de guardar la licencia, ramificar sobre puede_atender y usar ids estables no hay esquema que lo exprese, y es la parte que importa.
¿Todavía no tenés un complemento dado de alta? Empezá por la página de complementos.
Contanos qué tenés andando
Si encaja, lo damos de alta y empezás a cobrarlo con el primer cliente que lo contrate.