Conchi Agent API V1
Resumen
La Conchi Agent API permite que un cliente externo envie mensajes de una conversacion a Conchi y reciba la respuesta final del agente mediante un webhook configurado para ese cliente externo.
El flujo de integracion es asincrono:
Cliente externo -> POST /api/v1/external-agent/turns -> Conchi Agent -> callback al webhook del cliente externo
V1 soporta:
- busqueda de productos con
product_search; - creacion de reservas con
reservation_create; - escalado a humano con
human_escalation; - callbacks de respuesta final en texto.
V1 no soporta consulta de pedidos, cancelacion de pedidos, pagos ni callbacks estructurados obligatorios para UI. Conchi puede anadir campos opcionales de forma compatible dentro de V1.
Conceptos
| Concepto | Descripcion |
|---|---|
externalClientId |
Identifica al cliente externo autenticado por la API key. No se envia en el body. Conchi lo deriva de la credencial bearer. |
externalAccountId |
Identifica una cuenta, farmacia, centro, workspace o scope dentro del cliente externo. Es opcional si el cliente no tiene subcuentas. |
externalEndCustomerId |
Identifica al usuario final que mantiene la conversacion dentro del canal del cliente externo. |
sessionId |
Identificador de la sesion 24h creada o reutilizada por Conchi. |
turnId |
Identificador del mensaje aceptado por Conchi. |
correlationId |
Identificador opcional enviado por el cliente externo y devuelto en el callback. Sirve para correlacionar request y respuesta. |
requestId |
Identificador opcional de la peticion del cliente externo. Si no se envia, Conchi usa correlationId o genera uno. |
Sesiones 24h
Una sesion facturable de External Agent se reutiliza durante 24 horas cuando coinciden:
externalClientId + externalAccountId + externalEndCustomerId
Si el cliente externo no tiene subcuentas y omite externalAccountId, Conchi lo normaliza internamente a default. Si la integracion esta configurada con accountScopeRequired = true, externalAccountId pasa a ser obligatorio.
La conversacion del agente se aisla por:
externalClientId + externalAccountId + externalEndCustomerId + sessionId
Entornos
Usar siempre el host de API, no el host de la aplicacion/admin.
| Entorno | Base URL |
|---|---|
| Staging | https://staging-api.conchi.ai |
| Produccion | https://api.conchi.ai |
La forma de URL es la misma en staging y produccion:
POST {baseUrl}/api/v1/external-agent/turns
Antes de pasar a produccion, el cliente externo debe validar en staging:
- credencial de entrada;
- validacion del payload;
- reutilizacion de sesion 24h;
- respuesta del agente;
- entrega del callback;
- autenticacion del callback, si aplica;
- comportamiento de reintentos.
Autenticacion de entrada
La llamada a Conchi usa bearer auth:
Authorization: Bearer <external_client_api_key>
La API key identifica al externalClientId. El cliente externo no debe enviar externalClientId en el body. Conchi rechaza campos legacy o alias que intenten sustituir la identidad autenticada.
Las claves internas de Conchi no sirven para autenticar llamadas de clientes externos.
Versionado
La version mayor esta en la URL:
/api/v1/...
Opcionalmente, el cliente externo puede enviar la fecha de contrato V1:
Conchi-API-Version: 2026-05-01
Politica V1:
- cambios compatibles: Conchi puede anadir campos opcionales;
- cambios no compatibles: se publicaran en
/api/v2/...; - callbacks V1 incluyen
apiVersion = "v1"yeventVersion, inicialmente2026-05-01.
Enviar un mensaje
POST /api/v1/external-agent/turns HTTP/1.1
Authorization: Bearer <external_client_api_key>
Conchi-API-Version: 2026-05-01
Content-Type: application/json
Ejemplo con cuenta externa
Usar externalAccountId cuando el cliente externo gestiona varias farmacias, centros, cuentas o workspaces y cada uno tiene sus propios usuarios finales.
{
"externalAccountId": "pharmacy-001",
"externalEndCustomerId": "customer-789",
"message": {
"type": "text",
"text": "Busco ibuprofeno 400"
},
"correlationId": "turn-2026-05-01-0001",
"requestId": "turn-2026-05-01-0001",
"capabilityHint": "product_search",
"metadata": {
"source": "web-chat"
}
}
Ejemplo sin subcuentas
Omitir externalAccountId solo si la integracion no separa farmacias, centros, workspaces o cuentas hijas.
{
"externalEndCustomerId": "app-user-789",
"message": {
"type": "text",
"text": "Busco ibuprofeno 400"
},
"correlationId": "turn-2026-05-01-0002",
"capabilityHint": "product_search"
}
Ejemplo de reserva
Para reservas, enviar idempotencyKey estable por intento logico de reserva. Esto evita duplicados si el cliente externo repite la misma operacion por timeout o reintento propio.
{
"externalAccountId": "pharmacy-001",
"externalEndCustomerId": "customer-789",
"message": {
"type": "text",
"text": "Si, reservalo"
},
"correlationId": "turn-2026-05-01-0003",
"requestId": "turn-2026-05-01-0003",
"capabilityHint": "reservation_create",
"idempotencyKey": "reservation-customer-789-2026-05-01-0001"
}
Campos del request
| Campo | Requerido | Limite | Descripcion |
|---|---|---|---|
externalAccountId |
Condicional | 1-160 chars | Cuenta/farmacia/workspace del cliente externo. Obligatorio si la integracion tiene accountScopeRequired = true; si no, se normaliza a default cuando se omite. |
externalEndCustomerId |
Si | 1-160 chars | Usuario final de la conversacion. Participa en la sesion 24h. |
message.type |
Si | text |
V1 solo acepta mensajes de texto. |
message.text |
Si | 1-8000 chars | Texto del usuario final. |
correlationId |
No | Max 160 chars | Id de correlacion del cliente externo. Conchi lo devuelve en el callback si se envio. |
requestId |
No | Max 160 chars | Id de request del cliente externo. Si se omite, Conchi usa correlationId o genera uno. |
idempotencyKey |
Condicional | Max 180 chars | Recomendado y requerido operativamente para creacion de reservas. |
capabilityHint |
No | Enum | Sugerencia de intencion: product_search, reservation_create o human_escalation. No habilita capabilities desactivadas. |
metadata |
No | Objeto JSON | Metadatos pequenos de integracion. No enviar PII innecesaria. |
Campos no soportados como externalClientId, externalClientAccountId, externalClientEndCustomerId, externalClientCustomerId o customerId no forman parte del contrato y pueden ser rechazados.
Respuesta de aceptacion
Si el mensaje se acepta para procesamiento asincrono, Conchi responde 202 Accepted:
{
"sessionId": "00000000-0000-0000-0000-000000000001",
"turnId": "00000000-0000-0000-0000-000000000002",
"requestId": "turn-2026-05-01-0001",
"status": "queued"
}
Esta respuesta no es la respuesta final del agente. La respuesta final llega por callback.
Callback de respuesta final
Conchi envia el callback al webhook configurado para el cliente externo o para el externalAccountId si existe override.
Metodo:
POST <external_client_webhook_url>
Content-Type: application/json
Payload V1:
{
"apiVersion": "v1",
"eventVersion": "2026-05-01",
"type": "conchi.agent.final_response",
"deliveryTarget": "external_agent_callback",
"createdAt": "2026-05-01T10:00:00.000Z",
"externalClientId": "00000000-0000-0000-0000-000000000010",
"externalAccountId": "pharmacy-001",
"externalEndCustomerId": "customer-789",
"sessionId": "00000000-0000-0000-0000-000000000001",
"turnId": "00000000-0000-0000-0000-000000000002",
"correlationId": "turn-2026-05-01-0001",
"status": "answered",
"message": "He encontrado estas opciones:\n\n1) Ibuprofeno 400 mg\n3.50 EUR\nDisponible"
}
Campos del callback
| Campo | Requerido | Descripcion |
|---|---|---|
apiVersion |
Si | Version mayor del contrato. En V1 siempre v1. |
eventVersion |
Si | Version del evento webhook configurada para el cliente externo. Valor inicial: 2026-05-01. |
type |
Si | Tipo de evento. Para respuesta final: conchi.agent.final_response. |
deliveryTarget |
Si | Siempre external_agent_callback para esta integracion. |
createdAt |
Si | Fecha ISO-8601 en la que Conchi creo el evento. |
externalClientId |
Si | Cliente externo autenticado que origino la conversacion. |
externalAccountId |
Si | Cuenta/farmacia/workspace normalizado. Puede ser default. |
externalEndCustomerId |
Si | Usuario final de la conversacion. |
sessionId |
Si | Sesion 24h usada por el turn. |
turnId |
Si | Turn aceptado por Conchi. |
correlationId |
No | Devuelto cuando el request original lo incluyo. |
status |
Si | Estado funcional de la respuesta: answered, escalated, failed o needs_more_info. |
message |
Si | Texto final que debe mostrar el cliente externo al usuario final. |
structured |
No | Datos estructurados opcionales. No son obligatorios en V1 y deben tratarse como additive-only. |
El cliente externo debe renderizar message en su propia UI/canal. Los callbacks de External Agent no usan WhatsApp, Meta ni sender de Conchi.
Autenticacion del callback
El modo de autenticacion se configura en Admin por cliente externo o por override de externalAccountId.
Modo none
Conchi envia headers de evento, pero no envia secreto:
Content-Type: application/json
X-Conchi-Event-Id: <outbox_event_id>
X-Conchi-Timestamp: <unix_timestamp_seconds>
Modo bearer
Conchi envia el secreto configurado como bearer:
Content-Type: application/json
X-Conchi-Event-Id: <outbox_event_id>
X-Conchi-Timestamp: <unix_timestamp_seconds>
Authorization: Bearer <external_agent_callback_secret>
Modo hmac_sha256
Conchi firma el body exacto del callback:
Content-Type: application/json
X-Conchi-Event-Id: <outbox_event_id>
X-Conchi-Timestamp: <unix_timestamp_seconds>
X-Conchi-Signature-256: sha256=<hex_digest>
Entrada de firma:
{event_id}.{timestamp}.{raw_body}
Requisitos recomendados para el receptor:
- calcular HMAC-SHA256 con el secreto compartido;
- comparar firmas con comparacion en tiempo constante;
- rechazar timestamps fuera de una ventana de replay, normalmente 5 minutos;
- guardar
X-Conchi-Event-Iddurante la ventana de replay y rechazar duplicados; - usar el body crudo recibido, no el JSON reserializado.
Ejemplo de verificacion HMAC en Node.js:
import crypto from "node:crypto";
function verifyConchiCallback({ secret, eventId, timestamp, rawBody, signature }) {
const expectedHex = crypto
.createHmac("sha256", secret)
.update(`${eventId}.${timestamp}.${rawBody}`)
.digest("hex");
const receivedHex = signature.replace(/^sha256=/, "");
const expected = Buffer.from(expectedHex, "hex");
const received = Buffer.from(receivedHex, "hex");
if (expected.length !== received.length) return false;
return crypto.timingSafeEqual(expected, received);
}
Reintentos y timeouts de callback
Los callbacks de chat final response se entregan en tiempo real desde el outbox de Conchi.
Politica V1:
| Intento | Cuando se ejecuta |
|---|---|
| 1 | Inmediato tras crear el evento de callback. |
| 2 | 1 segundo despues del fallo del intento 1. |
| 3 | 3 segundos despues del fallo del intento 2. |
Detalles:
- timeout HTTP por intento: 3 segundos;
- cualquier respuesta
2xxse considera entregada; - respuestas no
2xx, errores de red o timeout se reintentan; - tras el tercer fallo, el evento queda en estado terminal
failed; - el cliente externo debe responder
2xxsolo cuando haya aceptado duraderamente el evento.
Errores de entrada
Formato comun:
{
"error": "INVALID_PAYLOAD",
"message": "INVALID_PAYLOAD",
"details": [
{
"path": "message.text",
"message": "String must contain at least 1 character(s)"
}
]
}
Errores habituales:
| HTTP | Codigo | Motivo |
|---|---|---|
400 |
INVALID_PAYLOAD |
El body no cumple el schema. |
400 |
INVALID_EXTERNAL_IDENTIFIER |
Un identificador externo esta vacio o supera 160 caracteres. |
400 |
EXTERNAL_ACCOUNT_ID_REQUIRED |
La integracion requiere externalAccountId y no se envio. |
401 |
EXTERNAL_AGENT_AUTH_REQUIRED |
Falta Authorization: Bearer .... |
401 |
EXTERNAL_CLIENT_AUTH_INVALID |
API key inexistente o invalida. |
401 |
EXTERNAL_CLIENT_CREDENTIAL_INACTIVE |
Credencial desactivada. |
401 |
EXTERNAL_CLIENT_CREDENTIAL_EXPIRED |
Credencial expirada. |
403 |
EXTERNAL_CLIENT_INACTIVE |
Cliente externo inactivo o deshabilitado. |
403 |
EXTERNAL_ACCOUNT_INACTIVE |
Cuenta externa bloqueada o inactiva por override. |
500 |
INTERNAL_ERROR |
Error inesperado. |
Ejemplos curl
Buscar producto
curl -i -X POST "${CONCHI_BASE_URL}/api/v1/external-agent/turns" \
-H "Authorization: Bearer ${CONCHI_EXTERNAL_AGENT_API_KEY}" \
-H "Conchi-API-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{
"externalAccountId": "pharmacy-001",
"externalEndCustomerId": "customer-789",
"message": { "type": "text", "text": "Busco ibuprofeno 400" },
"correlationId": "turn-search-001",
"requestId": "turn-search-001",
"capabilityHint": "product_search"
}'
Crear reserva
curl -i -X POST "${CONCHI_BASE_URL}/api/v1/external-agent/turns" \
-H "Authorization: Bearer ${CONCHI_EXTERNAL_AGENT_API_KEY}" \
-H "Conchi-API-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{
"externalAccountId": "pharmacy-001",
"externalEndCustomerId": "customer-789",
"message": { "type": "text", "text": "Si, reservalo" },
"correlationId": "turn-reservation-001",
"requestId": "turn-reservation-001",
"capabilityHint": "reservation_create",
"idempotencyKey": "reservation-customer-789-001"
}'
Escalar a humano
curl -i -X POST "${CONCHI_BASE_URL}/api/v1/external-agent/turns" \
-H "Authorization: Bearer ${CONCHI_EXTERNAL_AGENT_API_KEY}" \
-H "Conchi-API-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{
"externalAccountId": "pharmacy-001",
"externalEndCustomerId": "customer-789",
"message": { "type": "text", "text": "Quiero hablar con una persona" },
"correlationId": "turn-human-001",
"requestId": "turn-human-001",
"capabilityHint": "human_escalation"
}'
Checklist de integracion
Antes de produccion:
- cliente externo creado y activo en Admin;
- credencial de entrada generada y compartida una sola vez;
- webhook staging configurado;
webhookEventVersionconfigurado como2026-05-01;- modo de autenticacion de callback elegido:
none,bearerohmac_sha256; - test webhook desde Admin ejecutado correctamente;
- receptor del cliente externo verifica HMAC o bearer si aplica;
- cliente externo guarda y deduplica
X-Conchi-Event-Id; - cliente externo devuelve
2xxsolo al aceptar el evento; - prueba real de
product_searchvalidada; - prueba real de
reservation_createconidempotencyKeyvalidada; - prueba real de
human_escalationvalidada si la capability esta habilitada; - reutilizacion de sesion 24h validada con mismo
externalAccountId + externalEndCustomerId; - creacion de sesion nueva validada con otro
externalEndCustomerIdoexternalAccountId; - contacto operativo y contacto de facturacion registrados.
OpenAPI
El contrato publico V1 importable en herramientas como Swagger o Postman esta en docs/openapi/conchi-agent-api-v1.yaml.