Saltar al contenido

API de BotPilot

Tu sistema hablando por WhatsApp

Todo lo que hace el CRM se puede hacer desde tu propio software: responder conversaciones, avisarle a un cliente de su factura, agendar una cita y preguntarle al asistente. 77 endpoints, autenticación con una llave y ejemplos que se copian y se pegan.

URL base
https://apis.sds-soft.co:5503/api/public/v1

Cada petición lleva tu llave en Authorization: Bearer bpk_live_… (o en X-API-Key). La llave se muestra una sola vez al crearla desde el CRM, y nunca debe ir en una página web ni en una aplicación de móvil: quien la tenga puede escribirle a tus clientes.

Cómo empezar

Todo va con tu API key en la cabecera. La key se muestra UNA sola vez al crearla: si la pierdes hay que generar otra. Nunca la pongas en una página web ni en una aplicación móvil — quien la tenga puede escribirle a tus clientes.

Necesita: Plan con API
GET /plan permiso: stats

Qué incluye tu plan y cuánto llevas consumido.

Es la primera llamada que conviene hacer: dice qué canales tienes, si tienes IA y cuáles son tus topes. Si tu plan venció, responde «sinMembresia: true» junto a «vencida: true» y el plan que tenías, para que puedas avisar en tu pantalla; y si la cuenta nunca contrató nada, «nuncaTuvoPlan: true». En los dos casos «sinMembresia» es true: significa que no hay plan en vigor.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/plan \
  -H "Authorization: Bearer bpk_live_tu_llave"

Respuesta

{
  "caps": {
    "canalesPermitidos": ["cloud"],
    "incluyeIA": true,
    "envioPlantillas": true,
    "maxPlantillasDia": 500,
    "agenda": false
  },
  "consumo": { "mensajes": 1240, "conversaciones": 310 }
}

Mensajes y conversaciones

Responder conversaciones que ya existen y leer el historial. Para escribirle PRIMERO a alguien que no te ha escrito hace falta el grupo de Envíos iniciados: es otra cosa y tiene otras reglas.

Necesita: Plan con API
GET /conversations permiso: messages

Tu bandeja de entrada.

La caja de búsqueda va en `search` y busca en los CONTACTOS: nombre, correo y teléfono. Se busca ahí y no en la conversación porque la conversación no guarda ni el nombre ni el número, solo a quién pertenece.

Parámetros

search string — Nombre, correo o teléfono del contacto
status string — abierta | cerrada
modo string — ia | humano
canal string — cloud | baileys
funnelId string — Solo las de un embudo
estadoId string — Una etapa concreta: es una columna del tablero
contactId string — La conversación de esta persona, esté donde esté
scope string — sin_asignar: la cola de lo que espera a alguien
expand string — contacto,ultimoMensaje — la bandeja en UNA petición
page number — Página (por defecto 1)
limit number — Máximo 100

Ejemplo

curl "https://apis.sds-soft.co:5503/api/public/v1/conversations?search=marta&status=abierta&expand=contacto,ultimoMensaje" \
  -H "Authorization: Bearer bpk_live_tu_llave"
GET /conversations/:id/messages permiso: messages

El historial de una conversación.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/conversations/68a1f.../messages \
  -H "Authorization: Bearer bpk_live_tu_llave"
POST /conversations/:id/messages permiso: messages

Responder en una conversación abierta.

Solo funciona dentro de la ventana de 24 horas de Meta: si tu cliente no te escribe hace más de un día, WhatsApp no entrega este mensaje y no avisa de nada. Para esos casos está el envío por plantilla. Manda tu propia referencia en `clientRef` y un reintento no duplica: responde 200 con el mensaje de antes en vez de 201 con uno nuevo. Los tipos estructurados (botones, lista, ubicación, contactos) van en `payload`.

Cuerpo

{ "contentType": "texto", "texto": "Tu pedido ya salió", "clientRef": "pedido-8842" }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/conversations/68a1f.../messages \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"contentType":"texto","texto":"Tu pedido ya salió","clientRef":"pedido-8842"}'
POST /conversations/:id/close permiso: messages

Cerrar una conversación atendida.

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/conversations/68a1f.../close \
  -H "Authorization: Bearer bpk_live_tu_llave"
POST /conversations/:id/reopen permiso: messages

Volver a abrir una que se cerró.

Reabrir NO le manda nada al cliente: solo devuelve la conversación a la bandeja. Si la ventana de 24 horas ya se cerró, para escribirle sigue haciendo falta una plantilla.

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/conversations/68a1f.../reopen \
  -H "Authorization: Bearer bpk_live_tu_llave"
GET /conversations/:id permiso: messages

Una conversación con su contacto, su etapa y quién la atiende.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/conversations/68a1f... \
  -H "Authorization: Bearer bpk_live_tu_llave"
GET /conversations/queue permiso: messages

La cola: lo que espera a que alguien lo atienda.

Son las conversaciones que pidieron una persona y todavía no tienen a nadie asignado. Es la lista que mira quien coordina para repartir, distinta de la bandeja completa.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/conversations/queue \
  -H "Authorization: Bearer bpk_live_tu_llave"
POST /conversations/:id/read permiso: messages

Marcar como leída.

Pone a cero el contador de sin leer de ESA conversación. No manda el doble check azul al cliente: eso lo decide la línea, no esta llamada.

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/conversations/68a1f.../read \
  -H "Authorization: Bearer bpk_live_tu_llave"
POST /conversations/:id/pedir-numero permiso: messages

Pedirle al cliente su número de teléfono.

WhatsApp ya no siempre entrega el número de quien te escribe. **No hay forma de averiguarlo**: lo único que se puede hacer es pedírselo, y esto le muestra un botón para que lo comparta si quiere. Si acepta, su número queda guardado en el contacto y lo ves en `GET /contacts/:id`. El texto es opcional; el botón lo pone WhatsApp y no se puede cambiar. Solo para líneas de WhatsApp Api Cloud, y devuelve un 400 si ya tienes su número.

Cuerpo

{ "texto": "Para enviarte la cotización, ¿nos compartes tu número?" }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/conversations/68a1f.../pedir-numero \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"texto":"Para enviarte la cotización, ¿nos compartes tu número?"}'
POST /conversations/:id/notes permiso: messages

Dejar una nota interna.

La nota queda en el historial para tu equipo. «El cliente no la ve nunca.»

Cuerpo

{ "texto": "Pidió factura a nombre de la empresa" }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/conversations/68a1f.../notes \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"texto":"Pidió factura a nombre de la empresa"}'
PUT /conversations/:id/mode permiso: messages

Pasar la conversación al asistente o a una persona.

Con `humano` el asistente deja de contestar en esa conversación hasta que la devuelvas a `ia`. Es lo mismo que el interruptor del chat en el CRM.

Cuerpo

{ "modo": "humano" }

Ejemplo

curl -X PUT https://apis.sds-soft.co:5503/api/public/v1/conversations/68a1f.../mode \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"modo":"humano"}'
PUT /conversations/:id/state permiso: messages

Moverla de etapa en el embudo.

El `stateId` sale de `GET /funnels`: cada embudo trae sus etapas con su identificador. No se mueve por nombre, porque dos embudos pueden tener etapas que se llamen igual.

Cuerpo

{ "stateId": "6a2d733f0e1b4c5a9d8e7f60" }

Ejemplo

curl -X PUT https://apis.sds-soft.co:5503/api/public/v1/conversations/68a1f.../state \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"stateId":"6a2d733f0e1b4c5a9d8e7f60"}'
PUT /conversations/:id/group-ia permiso: messages

Encender o apagar el asistente en un GRUPO de WhatsApp.

Solo aplica a conversaciones de grupo. Un grupo con el asistente encendido contesta a todo el mundo que escriba dentro, así que viene apagado salvo que lo enciendas.

Cuerpo

{ "activa": true }

Ejemplo

curl -X PUT https://apis.sds-soft.co:5503/api/public/v1/conversations/68a1f.../group-ia \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"activa":true}'
POST /messages permiso: messages

Mandar a un número sin saber si ya existe la conversación.

Si esa persona ya te escribió, entra en su conversación; si no existe, la abre. ⚠️ Fuera de la ventana de 24 horas esto NO entrega: para eso está `POST /outreach` con plantilla. Con `clientRef` un reintento no duplica: la respuesta trae `repetido: true` cuando esa referencia ya se había mandado.

Cuerpo

{ "to": "573001234567", "contentType": "texto", "texto": "Tu pedido ya salió", "clientRef": "pedido-8842" }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/messages \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"to":"573001234567","contentType":"texto","texto":"Tu pedido ya salió","clientRef":"pedido-8842"}'
POST /media permiso: messages

Subir un archivo y quedarte con su `mediaRef`.

Va en base64 para no obligarte a armar un multipart. El `mediaRef` que devuelve es lo que se manda luego como adjunto de un mensaje, o lo que se indexa en la base de conocimiento.

Cuerpo

{ "contenidoBase64": "JVBERi0xLjQK...", "filename": "catalogo.pdf", "mimetype": "application/pdf" }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/media \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"contenidoBase64":"JVBERi0xLjQK...","filename":"catalogo.pdf","mimetype":"application/pdf"}'
GET /media/:ref permiso: messages

Una dirección temporal para descargar un adjunto.

Los mensajes con adjunto traen su `mediaRef`, no la dirección: los enlaces caducan y uno guardado en tu base deja de servir. Se pide en el momento de enseñarlo.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/media/6a61f8c2d4e5b6a7c8d9e0f1 \
  -H "Authorization: Bearer bpk_live_tu_llave"

Escribirle primero a un cliente

Avisarle a alguien de su factura, su cita o su pedido cuando no te ha escrito. En WhatsApp Api Cloud sale por una plantilla que Meta tiene que aprobar; en Wapi Code sale como un mensaje normal, espaciado entre 8 y 13 segundos para cuidar tu línea. En los dos casos el mensaje aterriza en el CRM como una conversación de verdad: si te responden, sigue ahí.

Necesita: Plan con APINecesita: Facultad de envíos iniciados
GET /templates permiso: templates

Tus plantillas aprobadas, y por qué las otras no sirven.

Vienen TODAS, también las que no se pueden usar, con el motivo exacto: "Meta todavía no la aprueba", "solo se permiten Utilidad y Autenticación", "lleva un botón de URL dinámica". Que una plantilla no aparezca en una lista no le dice a nadie qué arreglar. ⚠️ `usable` y `masiva` son DOS marcas distintas: `usable` responde «¿deja Meta mandarla?» y `masiva` responde «¿tiene sentido mandársela a toda una base?». La segunda es una decisión humana y opt-in: casi todas las usables NO son masivas, y sirven igual para escribirle a una persona.

Parámetros

lineId string — Obligatorio si tienes más de una línea

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/templates \
  -H "Authorization: Bearer bpk_live_tu_llave"

Respuesta

{
  "lineId": "68a1f...",
  "items": [
    { "nombre": "factura_lista", "idioma": "es", "estado": "APPROVED",
      "categoria": "UTILITY", "variables": 3, "usable": true, "masiva": true, "motivo": null },
    { "nombre": "recordatorio_cita", "idioma": "es", "estado": "APPROVED",
      "categoria": "UTILITY", "variables": 2, "usable": true, "masiva": false, "motivo": null },
    { "nombre": "promo_verano", "idioma": "es", "estado": "APPROVED",
      "categoria": "MARKETING", "variables": 2, "usable": false, "masiva": false,
      "motivo": "Solo se pueden enviar plantillas de categoría Utilidad o Autenticación." }
  ]
}
POST /outreach permiso: templates

Mandar el aviso.

Manda `referencia` con el identificador de tu sistema (el número de factura, el del ticket). Si repites la petición con la misma referencia se devuelve el envío de la primera vez en vez de mandar otro mensaje: así un reintento tuyo no le llega dos veces al cliente. En `to` va el número con indicativo **o** el `bsuid` del contacto, que es lo que da WhatsApp cuando no comparte el número de quien te escribe. Lo único que no le llega a un `bsuid` son las plantillas de Autenticación de un toque, cero toques y código para copiar.

Cuerpo

{
  "to": "573001234567",
  "template": "factura_lista",
  "variables": ["Carolina", "FV-2024-119", "$180.000"],
  "referencia": "FV-2024-119"
}

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/outreach \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"to":"573001234567","template":"factura_lista","variables":["Carolina","FV-2024-119","$180.000"],"referencia":"FV-2024-119"}'

Respuesta

202 Accepted
{ "conversationId": "68a2...", "messageId": "68a3...", "estado": "encolado" }

Errores propios

PLANTILLA_NO_APROBADA — Meta todavía no la aprueba.
CATEGORIA_NO_PERMITIDA — Es de Marketing. Solo se permiten Utilidad y Autenticación.
VARIABLES_NO_CUADRAN — La plantilla lleva otro número de variables.
VARIABLE_VACIA — Meta rechaza los envíos con una variable en blanco. Dice cuál.
VARIABLE_CON_SALTO — Una variable lleva un salto de línea, una tabulación o cinco espacios seguidos.
IDIOMA_AMBIGUO — La plantilla está aprobada en varios idiomas. Manda `idioma`.
TOPE_DIARIO_PLANTILLAS — Se acabó tu cupo del día. Se reinicia mañana.

Recuperar un lead que se enfrió

Alguien te escribió, nadie contestó y pasaron 24 horas: Meta cierra la ventana y a partir de ahí NO entrega mensajes escritos a mano — y no avisa, simplemente no llegan. Esto la reabre con una plantilla aprobada. Si la persona responde, vuelves a tener 24 horas para hablarle normal. Solo aplica en WhatsApp Api Cloud: en Wapi Code esa ventana no existe.

Necesita: Plan con APINecesita: Recuperación de leadsNecesita: Línea de WhatsApp Api Cloud
GET /recuperacion permiso: templates

Qué conversaciones se pueden reabrir hoy, y cuántos intentos le quedan a cada una.

Ya vienen filtradas: solo las que perdieron la ventana, a las que NADIE respondió después de su último mensaje, y quitando a quien pidió que no le escribieran más. `restantes` es lo que de verdad importa: hay un TOPE POR CONTACTO —2 salvo que se cambie— y cuando llega a cero esa persona desaparece de la lista para siempre, no hasta mañana. ⚠️ `activa: false` llega con 200 y su `motivo`, no como un 403: hay que poder DECIR por qué no se puede en vez de dejar una pantalla en blanco. `plantilla` trae el TEXTO EXACTO que va a salir: enséñaselo a tu usuario antes de gastarle un intento a nadie, y léelo de aquí en vez de copiarlo, que una copia se separa del original.

Parámetros

limit number — Cuántas traer. Entre 1 y 100, por defecto 50

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/recuperacion \
  -H "Authorization: Bearer bpk_live_tu_llave"

Respuesta

{
  "activa": true, "tope": 2, "precio": 250, "moneda": "COP",
  "plantilla": { "nombre": "botpilot_recuperacion2", "idioma": "es",
                 "texto": "Hola 👋\n\nTe contactamos en relación con tu conversación anterior, que quedó pendiente…" },
  "items": [
    { "conversationId": "68a1f...", "contactId": "68a20...", "nombre": "Carolina Ruiz",
      "telefono": "573001234567", "ultimoMensajeEn": "2026-08-11T14:02:00.000Z",
      "intentos": 0, "restantes": 2 }
  ]
}
POST /recuperacion/:id permiso: templates

Reabrir esa conversación. CUESTA DINERO.

Manda la plantilla `botpilot_recuperacion2`, que se crea sola en tu cuenta de WhatsApp el día que se conecta la línea y que Meta tiene que aprobar antes de que sirva. Cada llamada gasta un intento de los que dice `restantes` y deja su fila de cobro al precio del día. No hace falta comprobar antes si la ventana está cerrada: si está abierta te lo dice y no gasta nada, porque escribirle a mano es gratis y llega mejor.

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/recuperacion/68a1f... \
  -H "Authorization: Bearer bpk_live_tu_llave"

Respuesta

202 Accepted
{ "conversationId": "68a1f...", "intentos": 1, "restantes": 1,
  "categoria": "UTILITY", "categoriaCambiada": false, "envioId": "68a4..." }

Errores propios

PLAN_SIN_RECUPERACION — Tu plan no incluye la recuperación de leads.
TOPE_RECUPERACION_CERO — Está en cero intentos por contacto: no se puede recuperar a nadie.
TOPE_RECUPERACION — Esta persona ya agotó sus intentos. No se reinicia.
VENTANA_ABIERTA — Todavía le puedes escribir a mano, que es gratis. No se gastó nada.
CANAL_SIN_VENTANA — Es una conversación de Wapi Code: ahí no hay ventana de 24 horas.
CONTACTO_NO_INTERESADO — Esta persona pidió que no le escribieran más.
PLANTILLA_RECUPERACION_FALTA — Tu línea todavía no tiene la plantilla. Se pide al comprobar la línea.
PLANTILLA_RECUPERACION_NO_APROBADA — Existe, pero Meta aún no la aprueba.

Agenda de citas

Consultar los horarios libres, agendar, mover y cancelar. Es la misma agenda que ve tu equipo en el CRM y la que usa el asistente por WhatsApp: lo que agendas por aquí sale ahí, y al revés.

Necesita: Plan con APINecesita: Agenda
GET /agenda/servicios permiso: agenda

Qué se puede agendar y cuánto dura.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/agenda/servicios \
  -H "Authorization: Bearer bpk_live_tu_llave"
GET /agenda/disponibilidad permiso: agenda

Los huecos LIBRES, ya calculados.

Descuenta el horario de atención, las pausas, las citas que ya hay y los bloqueos. No hay que calcular nada: lo que devuelve es lo que se puede reservar.

Parámetros

servicioId string — Obligatorio
desde AAAA-MM-DD — Por defecto hoy
hasta AAAA-MM-DD — Por defecto dos semanas

Ejemplo

curl "https://apis.sds-soft.co:5503/api/public/v1/agenda/disponibilidad?servicioId=68b1...&desde=2026-08-03" \
  -H "Authorization: Bearer bpk_live_tu_llave"

Respuesta

{
  "servicio": { "id": "68b1...", "nombre": "Manicure clásica", "duracionMin": 45 },
  "tz": "America/Bogota",
  "dias": [
    { "dia": "2026-08-03", "horas": [
      { "hora": "09:00", "agentes": ["68c1...", "68c2..."] },
      { "hora": "09:30", "agentes": ["68c1..."] }
    ]}
  ]
}
POST /agenda/citas permiso: agenda

Agendar.

El día y la hora van en la zona de tu negocio, que es como los dice una persona. Si dos peticiones piden el mismo espacio a la vez, solo una lo consigue y la otra recibe ESPACIO_OCUPADO — nunca se agendan dos personas a la misma hora.

Cuerpo

{
  "servicioId": "68b1...",
  "contactId": "68d1...",
  "dia": "2026-08-03",
  "hora": "09:30",
  "referencia": "reserva-4821"
}

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/agenda/citas \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"servicioId":"68b1...","contactId":"68d1...","dia":"2026-08-03","hora":"09:30"}'

Errores propios

SIN_DISPONIBILIDAD — Esa hora no está entre las que se ofrecen.
ESPACIO_OCUPADO — Alguien lo tomó primero. Vuelve a consultar la disponibilidad.
MUY_PRONTO — El servicio exige más anticipación.
POST /agenda/citas/:id/cancelar permiso: agenda

Cancelar una cita.

Se le avisa al cliente por WhatsApp y el espacio queda libre inmediatamente.

Cuerpo

{ "motivo": "El cliente no puede" }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/agenda/citas/68e1.../cancelar \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"motivo":"El cliente no puede"}'
POST /agenda/citas/:id/reprogramar permiso: agenda

Mover una cita.

Si la hora nueva no está libre, la cita se queda donde estaba: no hay forma de que el cliente acabe sin ninguna de las dos.

Cuerpo

{ "dia": "2026-08-05", "hora": "14:00" }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/agenda/citas/68e1.../reprogramar \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"dia":"2026-08-05","hora":"14:00"}'
GET /agenda/citas permiso: agenda

Las citas que hay, con sus filtros.

Parámetros

desde string — Fecha inicial (YYYY-MM-DD)
hasta string — Fecha final (YYYY-MM-DD)
estado string — programada | cancelada | cumplida

Ejemplo

curl "https://apis.sds-soft.co:5503/api/public/v1/agenda/citas?desde=2026-09-01&hasta=2026-09-30" \
  -H "Authorization: Bearer bpk_live_tu_llave"

Voz y documentos

Convertir una nota de voz en texto, leer lo que dice una factura o una cédula, y hacer que un texto suene con una voz colombiana. Es lo mismo que usa tu asistente en WhatsApp, disponible para tu propio sistema.

Necesita: Plan con API
GET /media/uso permiso: media

Qué tienes contratado y cuánto llevas gastado este mes.

Cada cosa se mide en su unidad: la transcripción en minutos de audio, la lectura en páginas y la voz en caracteres. El contador se reinicia el día 1 de cada mes.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/media/uso \
  -H "Authorization: Bearer bpk_live_tu_llave"

Respuesta

{
  "periodo": "202607",
  "stt": { "habilitado": true, "unidad": "minutos de audio", "usado": 12.4, "tope": 100, "restante": 87.6 },
  "ocr": { "habilitado": true, "unidad": "páginas", "usado": 38, "tope": 500, "restante": 462 },
  "tts": { "habilitado": false, "unidad": "caracteres", "usado": 0, "tope": 0, "restante": null },
  "clone": { "habilitado": false, "unidad": "voces vivas", "usado": 0, "tope": 0 }
}
POST /media/transcribir permiso: media

Una nota de voz, convertida en texto.

Acepta el mismo formato en que WhatsApp manda las notas de voz (OGG/Opus), y también MP3, WAV o M4A. Hasta 25 MB. Es la operación más lenta de todas: alrededor de un segundo por cada segundo de audio con `small`, el doble con `medium` a cambio de más precisión. «Pon el tiempo de espera de tu cliente en 300 segundos»: casi todos los fallos que se ven aquí son impaciencia del que llama, no del servicio.

Cuerpo

{
  "contenidoBase64": "T2dnUwACAAAA...",
  "filename": "nota.ogg",
  "mimetype": "audio/ogg",
  "modelo": "small"
}

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/media/transcribir \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"contenidoBase64":"T2dnUwACAAAA...","filename":"nota.ogg","mimetype":"audio/ogg"}'

Respuesta

{
  "texto": "Buenos días, quiero agendar una cita para el lunes",
  "idioma": "es",
  "audioSegundos": 5.98,
  "minutosFacturados": 0.1,
  "modelo": "small"
}

Errores propios

MEDIA_NO_EN_PLAN — Tu plan no incluye transcripción de audio.
MEDIA_CUOTA_AGOTADA — Se acabaron los minutos del mes.
MEDIA_ARCHIVO_MUY_GRANDE — El audio pesa más de 25 MB.
MEDIA_FORMATO_NO_SOPORTADO — Ese formato de audio no se puede leer.
POST /media/leer permiso: media

Lo que dice una imagen o un PDF: texto, montos y NITs.

LEE TEXTO; no describe imágenes. Con una factura, una cédula, un comprobante de pago o una captura de pantalla funciona muy bien. Con la foto de un producto o de un daño devuelve el texto vacío, porque ahí no hay nada escrito que leer. Mira SIEMPRE `revisarHumano`: cuando viene en `true` la confianza bajó de 80 y el texto no se puede tratar como dato firme — conviene confirmarlo con la persona antes de usarlo. Los NIT se validan con el dígito de verificación oficial de la DIAN.

Cuerpo

{
  "contenidoBase64": "JVBERi0xLjQK...",
  "filename": "factura.pdf",
  "mimetype": "application/pdf"
}

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/media/leer \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"contenidoBase64":"JVBERi0xLjQK...","filename":"factura.pdf","mimetype":"application/pdf"}'

Respuesta

{
  "texto": "SALON AURORA SAS\nNIT 901100011-9\nTOTAL A PAGAR $ 105.000",
  "campos": [{ "clave": "Cliente", "valor": "Carolina Restrepo" }],
  "montos": ["$ 105.000"],
  "nits": [{ "numero": "901100011", "dv": 9, "valido": true }],
  "confianza": 100,
  "revisarHumano": false,
  "paginas": 1,
  "motor": "pdf-texto"
}

Errores propios

MEDIA_NO_EN_PLAN — Tu plan no incluye lectura de documentos.
MEDIA_CUOTA_AGOTADA — Se acabaron las páginas del mes.
POST /media/hablar permiso: media

Un texto, convertido en audio con voz colombiana.

Devuelve un WAV en base64 (22.05 kHz mono). Hasta 3000 caracteres. `voz` acepta una del catálogo neutral o el nombre de una que hayas clonado; si no mandas nada, usa `f1`.

Cuerpo

{ "texto": "Tu pedido ya salió y llega mañana.", "voz": "f1" }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/media/hablar \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"texto":"Tu pedido ya salió y llega mañana.","voz":"f1"}'

Respuesta

{ "audioBase64": "UklGRi4A...", "mimetype": "audio/wav", "voz": "f1", "caracteres": 34 }

Errores propios

MEDIA_NO_EN_PLAN — Tu plan no incluye respuesta con voz.
MEDIA_TEXTO_MUY_LARGO — El texto supera los 3000 caracteres.
MEDIA_VOZ_NO_ENCONTRADA — Esa voz no existe o no es de tu empresa.
GET /media/voces permiso: media

Las voces que puedes usar.

Las neutrales del catálogo (`f1` y `m1` son las estables) y las que tu empresa haya clonado. Las voces clonadas se crean desde el CRM, no por API: clonar la voz de una persona no es algo que deba poder dispararse con una llave de integración.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/media/voces \
  -H "Authorization: Bearer bpk_live_tu_llave"

Contactos

Tu libreta de clientes, la misma que ve el CRM.

Necesita: Plan con API
GET /contacts permiso: contacts

Listar y buscar.

Un contacto puede venir **sin teléfono**: WhatsApp ya no siempre lo entrega y en su lugar manda un identificador propio de tu empresa (`bsuid`, del estilo `CO.1121892226839719`). Ese contacto se atiende igual —su conversación y sus respuestas funcionan como siempre—; lo único que no se puede es escribirle tecleando su número.

Parámetros

search string — Busca por nombre o por número

Ejemplo

curl "https://apis.sds-soft.co:5503/api/public/v1/contacts?search=carolina" \
  -H "Authorization: Bearer bpk_live_tu_llave"
POST /contacts permiso: contacts

Crear un contacto.

`canal` es obligatorio y dice por qué línea se le va a escribir: `cloud` si tienes WhatsApp Api Cloud, `baileys` si tienes Wapi Code. El teléfono va completo, con indicativo de país y sin signos.

Cuerpo

{
  "nombre": "Carolina Restrepo",
  "telefono": { "celularCompleto": "573001234567" },
  "canal": "cloud",
  "email": "caro@ejemplo.com"
}

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/contacts \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"nombre":"Carolina Restrepo","telefono":{"celularCompleto":"573001234567"},"canal":"cloud"}'
GET /contacts/:id permiso: contacts

Un contacto con sus etiquetas y sus campos propios.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/contacts/6a2d733f... \
  -H "Authorization: Bearer bpk_live_tu_llave"
PATCH /contacts/:id permiso: contacts

Cambiarle algo a un contacto.

Solo se toca lo que mandes. `camposPersonalizados` es tuyo: mete ahí el identificador que uses en tu sistema y así puedes cruzar los dos lados sin guardar una tabla aparte.

Cuerpo

{ "nombre": "Marta Pérez", "etiquetas": ["mayorista"] }

Ejemplo

curl -X PATCH https://apis.sds-soft.co:5503/api/public/v1/contacts/6a2d733f... \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"nombre":"Marta Pérez","etiquetas":["mayorista"]}'
PATCH /contacts/:id/no-interesado permiso: contacts

Marcar un lead como perdido, o devolverlo.

Marcado deja de aparecer en `GET /recuperacion` y no se le vuelve a escribir. Se manda el valor y no se alterna solo: alternar depende de lo que hubiera antes, y dos llamadas a la vez lo dejarían al revés de lo que se quería.

Cuerpo

{ "noInteresado": true }

Ejemplo

curl -X PATCH https://apis.sds-soft.co:5503/api/public/v1/contacts/6a2d733f.../no-interesado \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"noInteresado":true}'
DELETE /contacts/:id permiso: contacts

Borrar un contacto.

⚠️ Se lleva por delante sus conversaciones y su historial. No hay vuelta atrás.

Ejemplo

curl -X DELETE https://apis.sds-soft.co:5503/api/public/v1/contacts/6a2d733f... \
  -H "Authorization: Bearer bpk_live_tu_llave"
POST /contacts/import permiso: contacts

Meter un contacto que ya tenías en tu sistema.

Distinto de `POST /contacts`: este no falla si el número ya existe, lo actualiza. Es el que se usa para volcar una base entera sin tener que preguntar antes por cada uno.

Cuerpo

{ "telefono": { "celularCompleto": "573001234567" }, "canal": "cloud", "nombre": "Marta Pérez" }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/contacts/import \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"telefono":{"celularCompleto":"573001234567"},"canal":"cloud","nombre":"Marta Pérez"}'
POST /contacts/:id/conversation permiso: contacts

Abrirle una conversación a un contacto.

Crea la conversación vacía y te devuelve su id, para poder asignarla o dejarle una nota antes de que haya un solo mensaje. ⚠️ Abrirla no le escribe nada al cliente.

Cuerpo

{ "whatsappLineId": "6a61f8c2d4e5b6a7c8d9e0f1" }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/contacts/6a2d733f.../conversation \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"whatsappLineId":"6a61f8c2d4e5b6a7c8d9e0f1"}'

El asistente

Usar el mismo asistente que atiende tu WhatsApp desde tu propio sistema, con tus instrucciones y tu base de conocimiento. El modelo lo pone BotPilot y no se elige por API: de él dependen el coste, la calidad y a quién se le reclama cuando responde mal.

Necesita: Plan con APINecesita: Asistente de IA
POST /ai/chat permiso: ai

Preguntarle al asistente.

Va la conversación entera en `messages`, no un solo texto: así el asistente sabe de qué se venía hablando. `role` es `user` para lo que escribe la persona y `assistant` para lo que ya respondió el asistente. ⚠️ No admite `model`: si lo mandas, responde 400 diciendo por qué, en vez de ignorarlo y dejarte creer que contestó el modelo que pediste.

Cuerpo

{
  "messages": [
    { "role": "user", "content": "¿Cuánto cuesta el plan familiar?" }
  ]
}

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/ai/chat \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"messages":[{"role":"user","content":"¿Cuánto cuesta el plan familiar?"}]}'
GET /ai/prompt permiso: config

Leer las instrucciones que tiene ahora el asistente.

Existía desde el principio y no estaba escrito aquí, así que el `PUT` de abajo era a ciegas: se podía pisar el prompt de una empresa sin forma de ver antes qué decía ni de guardarse una copia. Léelo, cámbiale lo que quieras y vuelve a mandarlo entero.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/ai/prompt \
  -H "Authorization: Bearer bpk_live_tu_llave"
PUT /ai/prompt permiso: config

Cambiar las instrucciones del asistente.

Se manda lo que quieras cambiar y nada más. `modo` decide si el asistente contesta solo, solo sugiere, o está apagado. ⛔ La **temperatura** y el **largo máximo de la respuesta** no se ajustan por aquí: los decide BotPilot, igual que el modelo. Mandarlos devuelve un 400 que lo explica, en vez de ignorarlos en silencio. Lo que sí puedes ajustar es cuánta conversación recuerda el asistente: `historyLimit` en `PATCH /lines/:id`, de 1 a 15.

Cuerpo

{ "systemPrompt": "Eres el asistente de...", "modo": "simple" }

Ejemplo

curl -X PUT https://apis.sds-soft.co:5503/api/public/v1/ai/prompt \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"systemPrompt":"Eres el asistente de..."}'

Enseñarle a la IA (base de conocimiento)

El asistente contesta con lo que la empresa le cargue aquí. Se sube un documento o un texto, se indexa, y a partir de ahí responde con eso en vez de inventar.

Necesita: Plan con APINecesita: Asistente de IA
GET /knowledge permiso: config

Todo lo que el asistente tiene aprendido.

Cada fuente trae su `estadoIndexacion`. Mientras esté en `procesando` el asistente todavía no la usa: la indexación va en segundo plano y un documento grande tarda.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/knowledge \
  -H "Authorization: Bearer bpk_live_tu_llave"
POST /knowledge permiso: config

Enseñarle algo nuevo.

Con `tipo: "texto"` va el contenido directo. Con `tipo: "documento"` va el `mediaRef` de un archivo que subiste antes con `POST /media`. Se admiten PDF, Word, texto, Markdown y CSV. ⚠️ Responde «202»: aceptado, todavía no indexado. ⚠️ «Un PDF escaneado es una foto y no tiene texto»: queda en `estadoIndexacion: "error"` con `codigoError: "DOC_SIN_TEXTO"`, a propósito. Antes decía «indexado» y el cliente creía que su asistente había aprendido algo que no aprendió.

Cuerpo

{ "tipo": "documento", "titulo": "Catálogo 2026", "mediaRef": "6a61f8c2d4e5b6a7c8d9e0f1" }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/knowledge \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"tipo":"documento","titulo":"Catálogo 2026","mediaRef":"6a61f8c2d4e5b6a7c8d9e0f1"}'
GET /knowledge/:id permiso: config

Cómo salió la indexación de una fuente.

Trae `chunksIndexados` y `extraccion.efectividad`, que dice qué porcentaje de páginas tenían texto de verdad. Es lo que hay que mirar antes de dar por bueno un documento.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/knowledge/6a61f8c2d4e5b6a7c8d9e0f1 \
  -H "Authorization: Bearer bpk_live_tu_llave"
POST /knowledge/:id/reindex permiso: config

Volver a indexar una fuente.

Para cuando cambiaste el archivo, o cuando la primera vez falló y ya lo arreglaste.

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/knowledge/6a61f8c2.../reindex \
  -H "Authorization: Bearer bpk_live_tu_llave"
DELETE /knowledge/:id permiso: config

Que el asistente lo olvide.

Borra la fuente y lo que se indexó de ella. Deja de usarla en la siguiente respuesta.

Ejemplo

curl -X DELETE https://apis.sds-soft.co:5503/api/public/v1/knowledge/6a61f8c2... \
  -H "Authorization: Bearer bpk_live_tu_llave"

Que la IA consulte tus datos reales

Para que el asistente pueda contestar «¿cuánto debo?» con la cifra de verdad, registras un endpoint tuyo y él decide solo cuándo llamarlo. La «descripción» es lo que lo decide: escríbela pensando en cuándo quieres que lo use, no en qué devuelve.

Necesita: Plan con APINecesita: Asistente de IA
GET /data-endpoints permiso: config

Los endpoints que tienes registrados.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/data-endpoints \
  -H "Authorization: Bearer bpk_live_tu_llave"
POST /data-endpoints permiso: config

Registrar uno.

Con `GET` los argumentos viajan en la querystring; con `POST`/`PUT`/`PATCH`, en el cuerpo. Los `headers` se guardan «cifrados» y sirven para autenticar contra tu API.

Cuerpo

{
  "titulo": "Estado de cuenta",
  "descripcion": "Devuelve el saldo y las facturas pendientes de un cliente por su documento. Úsalo cuando pregunte por su saldo o cuánto debe.",
  "url": "https://api.tuempresa.com/v1/cuenta",
  "metodo": "POST",
  "camposEnviar": [{ "nombre": "documento", "tipo": "string", "descripcion": "Cédula del cliente", "requerido": true }]
}

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/data-endpoints \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"titulo":"Estado de cuenta","descripcion":"Devuelve el saldo y las facturas pendientes de un cliente por su documento.","url":"https://api.tuempresa.com/v1/cuenta","metodo":"POST","camposEnviar":[{"nombre":"documento","tipo":"string","requerido":true}]}'
POST /data-endpoints/:id/test permiso: config

Probarlo ANTES de dejarlo suelto.

Lo llama de verdad con los datos que le pases y te devuelve el estado, lo que tardó y una muestra de la respuesta. Un endpoint que tarda cinco segundos deja al cliente esperando en WhatsApp, y eso solo se ve probándolo.

Cuerpo

{ "sampleBody": { "documento": "1090123456" } }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/data-endpoints/6a61f8c2.../test \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"sampleBody":{"documento":"1090123456"}}'
PATCH /data-endpoints/:id permiso: config

Cambiarle algo, o apagarlo.

Con `estado: "inactivo"` el asistente deja de llamarlo sin que tengas que borrarlo.

Cuerpo

{ "estado": "inactivo" }

Ejemplo

curl -X PATCH https://apis.sds-soft.co:5503/api/public/v1/data-endpoints/6a61f8c2... \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"estado":"inactivo"}'
DELETE /data-endpoints/:id permiso: config

Quitarlo.

Ejemplo

curl -X DELETE https://apis.sds-soft.co:5503/api/public/v1/data-endpoints/6a61f8c2... \
  -H "Authorization: Bearer bpk_live_tu_llave"

Tu equipo y el reparto

Quién atiende, cuánto aguanta cada uno y a quién le toca cada conversación. Es lo que hace falta para que un sistema tuyo reparta el trabajo en vez de hacerlo el CRM de BotPilot.

Necesita: Plan con API
GET /agents permiso: agents

Tu equipo: quién es cada uno, quién está disponible y cuánto lleva encima.

Cada agente trae `nombre`, `inicial` y `email` además de su `userId`. Si el servicio de identidad no responde, los nombres llegan en `null` y el resto del listado sigue sirviendo: un equipo sin nombres se puede repartir, uno que no llega no.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/agents \
  -H "Authorization: Bearer bpk_live_tu_llave"
PATCH /agents/:userId permiso: agents

Si atiende al público y con qué horario.

`atiendePublico: false` lo saca del reparto sin quitarle la cuenta: sigue entrando al CRM y viendo lo suyo, pero no le caen conversaciones. `heredaHorario: true` le pone el horario del negocio; con `false` manda el suyo propio, que va en `horario`. `horario` lleva `dias` (de 0 a 6, donde 0 es domingo) con sus `franjas` de `desde`/`hasta` en formato 24 h, y opcionalmente `timezone`. Un día con `activo: false` no atiende. ⚠️ Esta ruta NO tiene topes de carga: no existen en ningún nivel. Quien pueda atender, atiende.

Cuerpo

{ "atiendePublico": true, "heredaHorario": false, "horario": { "timezone": "America/Bogota", "dias": [{ "dia": 1, "activo": true, "franjas": [{ "desde": "08:00", "hasta": "18:00" }] }] } }

Ejemplo

curl -X PATCH https://apis.sds-soft.co:5503/api/public/v1/agents/6a2d733f... \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"atiendePublico":true,"heredaHorario":true}'
POST /conversations/:id/assign permiso: agents

Dársela a alguien concreto.

Cuerpo

{ "agentUserId": "6a2d733f0e1b4c5a9d8e7f60" }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/conversations/68a1f.../assign \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"agentUserId":"6a2d733f0e1b4c5a9d8e7f60"}'
POST /conversations/:id/auto-assign permiso: agents

Que la reparta BotPilot.

Elige a quien esté disponible, en su horario y con menos carga. ⚠️ «En un bucle no reparte»: llamado en paralelo para veinte conversaciones se las lleva la misma persona, porque cada llamada mira la carga antes de que las otras hayan escrito la suya.

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/conversations/68a1f.../auto-assign \
  -H "Authorization: Bearer bpk_live_tu_llave"
POST /conversations/reparto/simular permiso: agents

Ver cómo quedaría el reparto de un lote, SIN tocar nada.

Devuelve a quién le tocaría cada conversación y cómo queda la carga de cada persona. No escribe nada: es para enseñarlo antes de aplicarlo.

Cuerpo

{ "conversationIds": ["68a1f8c2d4e5b6a7c8d9e0f1", "68a1f8c2d4e5b6a7c8d9e0f2"] }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/conversations/reparto/simular \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"conversationIds":["68a1f8c2d4e5b6a7c8d9e0f1","68a1f8c2d4e5b6a7c8d9e0f2"]}'
POST /conversations/reparto permiso: agents

Repartir un lote entero, nivelando la carga.

⚠️ No es lo mismo que llamar a `auto-assign` en bucle, y por eso existe aparte: aquel mira la carga de cada persona antes de que las otras llamadas hayan escrito la suya, así que veinte conversaciones en paralelo se las lleva la misma. Esto reparte las N de una vez y nivela la carga FINAL. Aplicar RECALCULA en vez de fiarse de lo que simulaste, porque entre lo uno y lo otro pudo entrar trabajo.

Cuerpo

{ "conversationIds": ["68a1f8c2d4e5b6a7c8d9e0f1", "68a1f8c2d4e5b6a7c8d9e0f2"] }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/conversations/reparto \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"conversationIds":["68a1f8c2d4e5b6a7c8d9e0f1","68a1f8c2d4e5b6a7c8d9e0f2"]}'
POST /conversations/:id/release permiso: agents

Soltarla y devolverla a la cola.

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/conversations/68a1f.../release \
  -H "Authorization: Bearer bpk_live_tu_llave"

Configuración de la cuenta

Los datos de la empresa, el horario de atención, las líneas de WhatsApp y los embudos. Todo lo que en el CRM se toca a mano, para poder tocarlo desde tu sistema.

Necesita: Plan con API
GET /company permiso: config

La ficha de tu empresa.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/company \
  -H "Authorization: Bearer bpk_live_tu_llave"
PATCH /company permiso: config

Cambiarle algo: nombre, textos automáticos, cómo se reparte.

`modoAsignacion` decide si las conversaciones se reparten solas o las toma quien quiera. Los campos `texto…` son lo que contesta BotPilot cuando no puede atender: mándalos vacíos y se queda callado, que casi nunca es lo que quieres.

Cuerpo

{ "nombreComercial": "Mi Empresa", "modoAsignacion": "automatica" }

Ejemplo

curl -X PATCH https://apis.sds-soft.co:5503/api/public/v1/company \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"nombreComercial":"Mi Empresa","modoAsignacion":"automatica"}'
GET /business-hours permiso: config

El horario de atención.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/business-hours \
  -H "Authorization: Bearer bpk_live_tu_llave"
PUT /business-hours permiso: config

Cambiar el horario.

🔴 «El horario apaga el reparto.» Fuera de él no se le asigna nada a nadie automáticamente, aunque estén conectados. Si el reparto «deja de funcionar», esto es lo primero que hay que mirar. Se manda la semana entera: lo que no venga, no existe. `dia` es 0 para el domingo y 6 para el sábado. Cada día lleva sus `franjas` —se admiten varias, para partir la jornada al mediodía— y sus `pausas`. Las horas van en `HH:mm` y se validan: una zona horaria inventada o un día 47 dejaban el horario sin significar nada.

Cuerpo

{
  "timezone": "America/Bogota",
  "dias": [
    { "dia": 1, "activo": true, "franjas": [{ "desde": "08:00", "hasta": "12:00" }, { "desde": "14:00", "hasta": "18:00" }] },
    { "dia": 0, "activo": false }
  ]
}

Ejemplo

curl -X PUT https://apis.sds-soft.co:5503/api/public/v1/business-hours \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"timezone":"America/Bogota","dias":[{"dia":1,"activo":true,"franjas":[{"desde":"08:00","hasta":"18:00"}]}]}'
GET /lines permiso: config

Tus líneas de WhatsApp y cómo están.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/lines \
  -H "Authorization: Bearer bpk_live_tu_llave"
PATCH /lines/:id permiso: config

Encender o apagar el asistente en una línea.

`iaActiva` es el interruptor de esa línea entera; `iaGruposActiva`, el de los grupos, que va aparte a propósito. `historyLimit` es cuántos mensajes de atrás lee el asistente, de 1 a 15.

Cuerpo

{ "iaActiva": true, "historyLimit": 12 }

Ejemplo

curl -X PATCH https://apis.sds-soft.co:5503/api/public/v1/lines/6a61f8c2... \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"iaActiva":true,"historyLimit":12}'
GET /funnels permiso: config

Tus embudos con sus etapas.

De aquí sale el `stateId` que necesita `PUT /conversations/:id/state`. Cada etapa trae además su MARCA (`tipo`): es por lo que hay que buscarla, nunca por el nombre, porque el nombre lo cambia el cliente cuando quiere.

Ejemplo

curl https://apis.sds-soft.co:5503/api/public/v1/funnels \
  -H "Authorization: Bearer bpk_live_tu_llave"
POST /funnels permiso: config

Crear un embudo.

Cuerpo

{ "nombre": "Ventas" }

Ejemplo

curl -X POST https://apis.sds-soft.co:5503/api/public/v1/funnels \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"nombre":"Ventas"}'
PATCH /funnels/:id permiso: config

Cambiarle el nombre o las etapas.

Cuerpo

{ "nombre": "Ventas 2026" }

Ejemplo

curl -X PATCH https://apis.sds-soft.co:5503/api/public/v1/funnels/6a61f8c2... \
  -H "Authorization: Bearer bpk_live_tu_llave" \
  -H "Content-Type: application/json" \
  -d '{"nombre":"Ventas 2026"}'
DELETE /funnels/:id permiso: config

Borrar un embudo.

Ejemplo

curl -X DELETE https://apis.sds-soft.co:5503/api/public/v1/funnels/6a61f8c2... \
  -H "Authorization: Bearer bpk_live_tu_llave"

Números y consumo

Lo mismo que pinta el panel de BotPilot, en crudo, para que lo pintes tú. Todas admiten `desde` y `hasta` en formato YYYY-MM-DD.

Necesita: Plan con API
GET /stats permiso: stats

El resumen: conversaciones, mensajes y cuántas atendió la IA.

Parámetros

desde string — YYYY-MM-DD
hasta string — YYYY-MM-DD

Ejemplo

curl "https://apis.sds-soft.co:5503/api/public/v1/stats?desde=2026-08-01&hasta=2026-08-31" \
  -H "Authorization: Bearer bpk_live_tu_llave"
GET /stats/mensajes permiso: stats

Mensajes por día, para dibujar la curva.

Ejemplo

curl "https://apis.sds-soft.co:5503/api/public/v1/stats/mensajes?desde=2026-08-01&hasta=2026-08-31" \
  -H "Authorization: Bearer bpk_live_tu_llave"
GET /stats/embudo permiso: stats

Cuántas conversaciones hay en cada etapa.

Ejemplo

curl "https://apis.sds-soft.co:5503/api/public/v1/stats/embudo?desde=2026-08-01&hasta=2026-08-31" \
  -H "Authorization: Bearer bpk_live_tu_llave"
GET /stats/ia permiso: stats

Qué parte la lleva el asistente y qué parte una persona.

Ejemplo

curl "https://apis.sds-soft.co:5503/api/public/v1/stats/ia?desde=2026-08-01&hasta=2026-08-31" \
  -H "Authorization: Bearer bpk_live_tu_llave"
GET /stats/agentes permiso: stats

Cuánto lleva cada persona del equipo.

Ejemplo

curl "https://apis.sds-soft.co:5503/api/public/v1/stats/agentes?desde=2026-08-01&hasta=2026-08-31" \
  -H "Authorization: Bearer bpk_live_tu_llave"
GET /usage permiso: stats

Lo que llevas gastado del plan.

Tokens de IA, mensajes y minutos de voz consumidos frente a lo que incluye tu plan. Es con lo que se avisa antes de quedarse sin cupo, no después.

Ejemplo

curl "https://apis.sds-soft.co:5503/api/public/v1/usage?desde=2026-08-01&hasta=2026-08-31" \
  -H "Authorization: Bearer bpk_live_tu_llave"

Recibir lo que entra

En vez de preguntar cada rato, te avisamos: cada mensaje que entra por tu WhatsApp llega a la URL que configures, firmado para que puedas comprobar que viene de nosotros.

Necesita: Plan con API
POST (tu URL)

Lo que te mandamos cuando entra un mensaje.

La cabecera `X-BotPilot-Signature` es el HMAC-SHA256 del cuerpo con tu secreto. Compruébala SIEMPRE antes de hacer nada: sin eso, cualquiera que sepa tu URL puede inventarse mensajes de tus clientes.

Ejemplo

// Node.js: comprobar la firma antes de confiar en el cuerpo
import { createHmac, timingSafeEqual } from 'node:crypto';

function firmaValida(cuerpoCrudo, cabecera, secreto) {
  const esperado = 'sha256=' + createHmac('sha256', secreto).update(cuerpoCrudo).digest('hex');
  const a = Buffer.from(esperado), b = Buffer.from(cabecera || '');
  // Comparación de tiempo constante: un === normal filtra la firma byte a byte.
  return a.length === b.length && timingSafeEqual(a, b);
}

Respuesta

POST https://tu-sistema.com/webhook
X-BotPilot-Signature: sha256=<hmac_del_cuerpo_con_tu_secreto>

{
  "event": "message.inbound",
  "conversationId": "68a1...",
  "canal": "cloud",
  "contact": { "nombre": "Carolina", "celular": "573001234567" },
  "message": { "direccion": "in", "contentType": "texto", "texto": "Hola", "at": "2026-07-26T05:00:00.000Z" }
}

Permisos de una llave

Cada llave se crea con los permisos que tú marcas. Dale lo justo: si una llave se filtra, solo abre lo que le diste. El de envíos iniciados es el que más cuidado merece — es el único que puede escribirle primero a alguien.

messages Leer y responder conversaciones.
templates Escribirle primero a un cliente. Es el que más cuidado merece.
agenda Consultar horarios, agendar, mover y cancelar citas.
media Transcribir audio, leer documentos y convertir texto en voz. Se paga por uso.
contacts Tu libreta de clientes.
agents Tu equipo y el reparto de conversaciones.
ai Preguntarle al asistente.
config Configuración: líneas, horarios, instrucciones del asistente.
stats Estadísticas y consumo.

Errores

Todos vienen con la misma forma, así que se pueden tratar en un solo sitio: { "error": { "code", "message" } }. Compara siempre por code, no por el texto: el mensaje puede cambiar, el código no.

401 SIN_APIKEY No mandaste la llave. Va en `Authorization: Bearer` o en `X-API-Key`.
401 APIKEY_INVALIDA La llave no existe, está revocada o va mal escrita.
403 SCOPE_INSUFICIENTE Tu llave no tiene ese permiso. Crea otra con los permisos que necesitas.
402 PLAN_SIN_API Tu plan no incluye la API pública.
402 SIN_MEMBRESIA La empresa no tiene un plan activo.
400 VALIDACION Falta un dato o viene mal. El mensaje dice cuál.
404 NO_ENCONTRADO Ese recurso no existe o no es tuyo.
409 CONFLICTO Algo cambió mientras tanto (por ejemplo, el espacio de una cita).
429 DEMASIADAS_PETICIONES Baja el ritmo y reintenta con espera creciente.
502 MEDIA_NO_CONFIG El servicio de voz y documentos no está conectado. No es culpa de tu petición.

¿Listo para conectar tu sistema?

Las llaves se crean desde el CRM, en la sección API. Ahí ves la misma documentación que esta, pero filtrada: solo lo que tu plan incluye, y qué te falta para el resto.

Entrar al CRM