Conchi Developers
Documentacion publica para integradores externos. El contrato OpenAPI se puede descargar desde esta misma pagina.

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:

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. Se devuelve para correlacion y callbacks; no se envia para elegir ni reabrir una sesion.
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 una ventana fija de 24 horas desde que Conchi crea la sesion. La identidad funcional de esa ventana es:

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.

Los mensajes enviados dentro de la ventana actualizan la actividad de la conversacion, pero no amplian la ventana facturable. Si el primer mensaje de una sesion se acepta el lunes a las 10:00:00, esa sesion puede reutilizarse hasta antes del martes a las 10:00:00. Un mensaje aceptado el martes a las 10:00:01 crea una nueva sesion para esa misma identidad externa.

El cliente externo no elige la sesion. Cada POST /api/v1/external-agent/turns se resuelve internamente con el externalClientId autenticado por la API key mas externalAccountId + externalEndCustomerId. El sessionId devuelto sirve para correlacionar la aceptacion, el callback y logs operativos. Si un cliente envia un sessionId antiguo como campo adicional del body, no se usa para seleccionar la sesion: Conchi vuelve a resolver la sesion vigente por identidad externa y devuelve el sessionId actual o uno nuevo si la ventana anterior ya ha terminado.

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:

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:

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 ingress 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. sessionId tampoco es un campo funcional de entrada; si aparece como campo adicional, no selecciona ni reabre una sesion.

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.

El sessionId de esta respuesta es el identificador resuelto por Conchi para esa identidad externa. Debe tratarse como valor de correlacion y no como input requerido para el siguiente turno.

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 Bloque estructurado opcional y additive-only. Cuando kind = "product_results", usar el contrato display-only descrito abajo.

El cliente externo debe renderizar message en su propia UI/canal. message es obligatorio y sigue siendo un fallback suficiente aunque structured este presente. Los callbacks de External Agent no usan WhatsApp, Meta ni sender de Conchi.

structured.kind = "product_results"

structured es opcional y additive-only: los clientes deben ignorar campos desconocidos y seguir funcionando con message si el bloque no existe o no pueden renderizarlo.

Cuando structured.kind es product_results, el bloque describe una estructura display-only para que el cliente externo renderice cards, carousel o lista en su propia UI. No es un payload de WhatsApp/Meta, no contiene IDs de quick reply firmados y no debe tratarse como una accion autenticada. V1 ingress sigue aceptando solo texto; actions[].textInput es una sugerencia para enviar un turno de texto posterior a POST /api/v1/external-agent/turns.

Para productos con availability = "pending_consult", el precio puede venir como price.amount = null y price.display = "A consultar". El cliente debe mostrar price.display en vez de construir su propio texto desde amount.

Ejemplo breve:

{
  "apiVersion": "v1",
  "eventVersion": "2026-05-01",
  "type": "conchi.agent.final_response",
  "deliveryTarget": "external_agent_callback",
  "createdAt": "2026-07-02T10: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-search-001",
  "status": "answered",
  "message": "He encontrado estos productos:\n\n1) Ibuprofeno 400 mg\n3.50 EUR\nDisponible\n\n2) Ibuprofeno Forte\n4.25 EUR\nLo podemos encargar",
  "structured": {
    "kind": "product_results",
    "version": "2026-07-02",
    "presentation": "carousel",
    "fallbackText": "He encontrado estos productos:\n\n1) Ibuprofeno 400 mg\n3.50 EUR\nDisponible\n\n2) Ibuprofeno Forte\n4.25 EUR\nLo podemos encargar",
    "groups": [
      {
        "groupId": "g1",
        "query": "ibuprofeno",
        "presentationKind": "multiple_options",
        "fallbackText": "He encontrado estos productos.",
        "items": [
          {
            "option": 1,
            "optionRef": "o1",
            "productId": "prod-1",
            "title": "Ibuprofeno 400 mg 20 comprimidos",
            "price": {
              "amount": 3.5,
              "currency": "EUR",
              "display": "3.50 EUR"
            },
            "availability": "available",
            "image": null,
            "badges": [],
            "actions": [
              {
                "type": "select",
                "label": "Reservar",
                "textInput": "quiero la opcion 1"
              }
            ]
          },
          {
            "option": 2,
            "optionRef": "o2",
            "productId": "prod-2",
            "title": "Ibuprofeno Forte",
            "price": {
              "amount": 4.25,
              "currency": "EUR",
              "display": "4.25 EUR"
            },
            "availability": "orderable",
            "image": null,
            "badges": [
              "recipe",
              "psychotropic"
            ],
            "actions": [
              {
                "type": "select",
                "label": "Encargar",
                "textInput": "quiero la opcion 2"
              }
            ]
          }
        ]
      }
    ]
  }
}

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:

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:

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:

OpenAPI

El contrato publico V1 importable en herramientas como Swagger o Postman esta en docs/openapi/conchi-agent-api-v1.yaml.