Download OpenAPI specification:
La API de dungo permite que tus propias aplicaciones —tu ERP, tu sitio, un script interno— lean y escriban los datos de tu cuenta: contactos, etiquetas y tratos.
Todo lo que hagas por la API se comporta igual que si lo hicieras desde la aplicación web: etiquetar un contacto o mover un trato dispara los mismos algoritmos, y cada cambio queda registrado en la auditoría de tu cuenta a nombre de la integración que lo hizo.
Cada llamada debe incluir tu API key en el header X-Api-Key:
X-Api-Key: dgk_7Fq2mZK9xR4tLbNc8vWe1A
La key identifica a tu cuenta: no necesitas enviar ningún otro identificador. Cada key solo ve y modifica los datos de la cuenta para la que fue creada.
En la aplicación web de dungo, con un usuario SUPER: Ajustes → API → Crear API key. Ahí eliges el nombre, los permisos y la caducidad.
La key completa se muestra una sola vez, al crearla. Guárdala en un lugar seguro (un gestor de secretos o las variables de entorno de tu servidor). Si la pierdes, revócala y crea una nueva. Después de crearla, en el listado solo se ven sus primeros caracteres.
La API está pensada para llamarse desde tu servidor, nunca desde el navegador ni desde una app móvil: cualquiera que vea el código podría leer la key.
Al crear la key eliges si caduca en días, meses, años, o si no caduca. Una key expirada deja de autenticar por sí sola. Puedes revocar cualquier key desde la misma pantalla, y el corte es inmediato.
Cada key lleva la lista de permisos que le asignaste. read habilita las lecturas (GET) y
write las escrituras (POST, PUT, DELETE) del recurso.
| Scope | Habilita |
|---|---|
contactos:read |
Listar y consultar contactos, y ver sus etiquetas |
contactos:write |
Crear, editar y eliminar contactos; etiquetar y desetiquetar |
etiquetas:read |
Listar el catálogo de etiquetas |
etiquetas:write |
Crear, editar y eliminar etiquetas |
tratos:read |
Leer tableros, etapas, tratos y su historial |
tratos:write |
Crear, mover y eliminar tratos |
Si llamas a un endpoint sin el scope necesario, la respuesta es 403 con el código
SCOPE_INSUFICIENTE. Da a cada integración solo los permisos que necesita.
Los listados aceptan page (base 0) y size (20 por omisión) y responden con esta forma:
{
"data": [ ... ],
"page": 0,
"size": 20,
"total": 137,
"totalPages": 7
}
Todos los errores comparten el mismo cuerpo:
{
"code": "NOT_FOUND",
"message": "Recurso no encontrado",
"details": "Contacto no encontrado: 0198f1c2-...",
"timestamp": "2026-08-03T14:22:31.482"
}
| HTTP | Código | Qué pasó |
|---|---|---|
| 400 | VALIDATION_ERROR |
Faltan campos o un valor es inválido |
| 401 | API_KEY_AUSENTE |
No enviaste el header X-Api-Key |
| 401 | API_KEY_INVALIDA |
La key no existe o está mal formada |
| 401 | API_KEY_EXPIRADA |
La key pasó su fecha de caducidad |
| 401 | API_KEY_REVOCADA |
La key fue revocada desde la aplicación web |
| 403 | SCOPE_INSUFICIENTE |
La key no tiene el permiso para esa operación |
| 404 | NOT_FOUND |
El recurso no existe en tu cuenta |
| 409 | CONFLICT |
Choca con el estado actual: por ejemplo, un teléfono que ya tiene otro contacto (el detalle trae su contactoId) |
| 429 | TOO_MANY_REQUESTS |
Superaste el límite de solicitudes |
| 500 | INTERNAL_ERROR |
Error inesperado del servidor |
Hasta 120 solicitudes por minuto por API key. Al superarlo recibes 429; reintenta
después de unos segundos. Si necesitas un volumen mayor, escríbenos.
Listar tus contactos:
curl -H "X-Api-Key: $DUNGO_API_KEY" \
"https://dungo.ai/api/v1/contactos?size=5"
Crear un contacto:
curl -X POST "https://dungo.ai/api/v1/contactos" \
-H "X-Api-Key: $DUNGO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"nombre":"Ana","apellido":"Perez","telefono":"56912345678","email":"ana@ejemplo.cl"}'
Etiquetar ese contacto:
curl -X POST "https://dungo.ai/api/v1/contactos/$CONTACTO_ID/etiquetas/$ETIQUETA_ID" \
-H "X-Api-Key: $DUNGO_API_KEY"
Mover un trato de etapa (el etapaId sale de GET /api/v1/tableros/{id}, que devuelve el
tablero con sus etapas en orden):
curl -X PUT "https://dungo.ai/api/v1/tratos/$TRATO_ID/etapa" \
-H "X-Api-Key: $DUNGO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"etapaId":"0198f1c2-4a7b-7c3d-9e21-5f6a7b8c9d01"}'
Mover un trato a una etapa de tipo ganado o perdido es un movimiento más: se usa este mismo endpoint.
El caso más común: cuando tu sistema registra un lead, quieres encontrarlo en dungo por su teléfono y etiquetarlo; si no existe, crearlo y etiquetarlo igual.
Eso es una sola llamada: POST /api/v1/contactos/sincronizar. Necesitas una key con
contactos:write y, para leer el catálogo de etiquetas la primera vez, etiquetas:read.
Una sola vez: obtén el etiquetaIdDun de la etiqueta que vas a aplicar con
GET /api/v1/etiquetas y guárdalo en la configuración de tu integración.
Por cada lead:
curl -X POST "https://dungo.ai/api/v1/contactos/sincronizar" \
-H "X-Api-Key: $DUNGO_API_KEY" -H "Content-Type: application/json" \
-d '{
"telefono": "+56 9 1234 5678",
"nombre": "Ana",
"apellido": "Perez",
"email": "ana@ejemplo.cl",
"empresa": "Ejemplo SpA",
"etiquetaIds": ["0198f1c2-4a7b-7c3d-9e21-5f6a7b8c9d01"]
}'
Respuesta:
{
"contacto": { "contactoIdDun": "0198f...", "nombre": "Ana", "telefono": "+56 9 1234 5678", … },
"creado": true,
"datosActualizados": false,
"etiquetasAgregadas": ["0198f1c2-4a7b-7c3d-9e21-5f6a7b8c9d01"],
"etiquetasYaPresentes": []
}
Qué te garantiza:
+56 9 1234 5678, 56912345678 y (56) 9 1234-5678
son el mismo contacto. Un teléfono identifica a un solo contacto dentro de tu cuenta.creado pasa a
false y las etiquetas que ya tenía vuelven en etiquetasYaPresentes, sin error."actualizarDatos": true si prefieres que los tuyos manden.tipoContactoId acepta CLIENTE, PROSPECTO, PROVEEDOR, COLABORADOR, EX_CLIENTE o
DEFAULT, y se aplica solo al crear: los contactos nuevos nacen con el tipo que envíes
(o DEFAULT si lo omites) y los que ya existen conservan el suyo, aunque mandes otro.
Es a propósito: si un CLIENTE vuelve a pasar por tu formulario de leads, no queremos
degradarlo a PROSPECTO ni disparar los algoritmos de ese cambio sin que nadie lo pida.
actualizarDatos: true tampoco toca el tipo. Para cambiarlo deliberadamente está
PUT /api/v1/contactos/{id}/tipo/{tipoContactoId}.
nombre solo es obligatorio cuando el contacto hay que crearlo. El código de respuesta es
201 si nació en esa llamada y 200 si ya existía.
Etiquetar dispara el trigger ETIQUETA_AGREGADA del motor de algoritmos: puedes colgar de
esa etiqueta una automatización en dungo —por ejemplo, que un agente de IA salude al lead—
sin escribir nada más en tu integración.
Si solo quieres consultar, telefonoExacto compara ignorando el formato:
curl -H "X-Api-Key: $DUNGO_API_KEY" \
"https://dungo.ai/api/v1/contactos?telefonoExacto=56912345678"
(El parámetro telefono, en cambio, busca por coincidencia parcial: 1234 también devuelve
991234567.)
Actualiza los campos enviados. Requiere el scope contactos:write.
| id required | string <uuid> |
| nombre | string |
| apellido | string |
| telefono | string |
string | |
| empresa | string |
| fechaNacimiento | string <date> |
| genero | string |
| tipoContactoId | string |
{- "nombre": "string",
- "apellido": "string",
- "telefono": "string",
- "email": "string",
- "empresa": "string",
- "fechaNacimiento": "2019-08-24",
- "genero": "string",
- "tipoContactoId": "string"
}Devuelve los contactos del tenant paginados. Los filtros son opcionales y se combinan entre si (AND). etiquetaIds acepta varios valores repitiendo el parametro. nombre, telefono y email buscan por coincidencia PARCIAL (contiene). Para encontrar a alguien por su numero usa telefonoExacto, que compara ignorando el formato: +56 9 1234 5678 y 56912345678 son el mismo telefono. Requiere el scope contactos:read.
| nombre | string |
| telefono | string |
| telefonoExacto | string |
string | |
| etiquetaIds | Array of strings <uuid> [ items <uuid > ] |
| page | integer <int32> Default: 0 |
| size | integer <int32> Default: 20 |
El unico campo obligatorio es nombre. El telefono es opcional, pero si viene debe tener entre 8 y 15 digitos y no puede estar tomado: un telefono identifica a un solo contacto dentro de tu cuenta, sin importar como se escriba, y un duplicado responde 409 con el id del contacto que ya lo tiene. Si estas sincronizando leads desde otro sistema, usa POST /api/v1/contactos/sincronizar, que resuelve buscar-o-crear en una sola llamada. Requiere el scope contactos:write.
| nombre | string |
| apellido | string |
string | |
| telefono | string |
| empresa | string |
| fechaNacimiento | string <date> |
| genero | string |
| tipoContactoId | string |
{- "nombre": "string",
- "apellido": "string",
- "email": "string",
- "telefono": "string",
- "empresa": "string",
- "fechaNacimiento": "2019-08-24",
- "genero": "string",
- "tipoContactoId": "string"
}Vincula una etiqueta existente al contacto y dispara el trigger ETIQUETA_AGREGADA de los algoritmos. NO es idempotente: si el contacto ya tiene esa etiqueta responde 400 VALIDATION_ERROR; tratalo como exito en tu integracion. Requiere el scope contactos:write.
| id required | string <uuid> |
| etiquetaId required | string <uuid> |
Pensado para sincronizar leads desde otro sistema en UNA sola llamada: busca el contacto por telefono —comparando sin importar el formato—, lo crea si no existe, y le aplica las etiquetaIds indicadas.
Es idempotente: repetir la misma llamada no duplica nada ni falla. Las etiquetas que el contacto ya tenia se devuelven en etiquetasYaPresentes en vez de dar error, y creado dice si el contacto se dio de alta ahora.
Todo ocurre en una transaccion, asi que dos llamadas simultaneas para el mismo lead no pueden crear dos contactos. nombre solo es obligatorio cuando hay que crearlo. Por omision NO se pisan los datos de un contacto que ya existe: para eso hay que enviar actualizarDatos: true.
tipoContactoId se aplica SOLO al crear. Un contacto que ya existe conserva su tipo aunque aqui se mande otro, para que un CLIENTE que vuelve a pasar por un formulario de leads no quede degradado a PROSPECTO. Para cambiarlo a proposito esta PUT /api/v1/contactos/{id}/tipo/{tipoContactoId}.
Requiere el scope contactos:write.
| telefono required | string Teléfono del contacto; es la clave de la sincronización. Se compara ignorando el formato: +56 9 1234 5678 y 56912345678 son el mismo. |
| nombre | string Obligatorio solo si el contacto no existe todavía. |
| apellido | string |
string | |
| empresa | string |
| tipoContactoId | string Tipo de contacto del catálogo (CLIENTE, PROSPECTO, PROVEEDOR, COLABORADOR, EX_CLIENTE, DEFAULT). Se aplica SOLO al crear: un contacto que ya existe conserva el tipo que tenía aunque aquí se mande otro. Si se omite, los contactos nuevos quedan con DEFAULT. |
| etiquetaIds | Array of strings <uuid> [ items <uuid > ] Etiquetas a aplicar. Las que el contacto ya tenga se informan aparte y no producen error. |
| actualizarDatos | boolean Default: "false" Si el contacto ya existe, sobrescribir nombre, apellido, email y empresa con los enviados. NO afecta al tipo de contacto, que nunca se pisa. Por omisión false: no se toca lo que ya está cargado en dungo. |
{- "telefono": 56912345678,
- "nombre": "Ana",
- "apellido": "string",
- "email": "string",
- "empresa": "string",
- "tipoContactoId": "PROSPECTO",
- "etiquetaIds": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "actualizarDatos": "false"
}Mueve el trato a etapaId, que debe pertenecer a su mismo tablero. Registra el movimiento en el historial y dispara el trigger TRATO_MOVIDO. Si el trato ya esta en esa etapa, no hace nada y responde 200. Requiere el scope tratos:write.
| id required | string <uuid> |
| etapaId | string <uuid> |
{- "etapaId": "e91a9f4e-55dd-4516-a0cd-ab987d07fbbc"
}Crea el trato en la etapa indicada y dispara el trigger TRATO_CREADO. Requiere el scope tratos:write.
| tableroId | string <uuid> |
| contactoId | string <uuid> |
| etapaId | string <uuid> |
| titulo | string |
{- "tableroId": "26f52223-d41c-4f15-ac8a-c33c393983b6",
- "contactoId": "e5cf4809-41f1-443c-947e-5cb346946b8d",
- "etapaId": "e91a9f4e-55dd-4516-a0cd-ab987d07fbbc",
- "titulo": "string"
}