Primeros 15 minutos con la API
Del alta de la aplicación a tu primer ticket: identificar al usuario, abrir una incidencia, pedir sugerencias de IA, enviar errores y tratar los fallos.
Actualizado el . 7 min de lectura
Incluido en los planes Profesional y Avanzado. Ver precios
Esta guía te lleva de cero a una integración que funciona: das de alta la aplicación, le concedes permisos, identificas a un usuario, abres un ticket en su nombre, pides sugerencias de IA y aprendes a tratar los errores sin que tu interfaz se rompa.
Necesitas la clave de API de una aplicación externa. Los ejemplos usan tickets de soporte, así que el plan de tu organización tiene que incluir el aplicativo Tickets de soporte con la capacidad «Tickets propios», además de Integraciones.
Esta es la conversación completa entre tu servidor y UTILIA OS; los pasos de abajo la construyen una petición cada vez:
Tu primera integración, de principio a fin
Participan: Tu servidor, UTILIA OS.
De Tu servidor a UTILIA OS:
POST /external/v1/users/identify
Da de alta a tu usuario con el externalId que tú eliges.De Tu servidor a UTILIA OS:
POST /external/v1/tickets
Abre un ticket en su nombre.De UTILIA OS a Tu servidor:
201 con el ticket creado (respuesta)
De Tu servidor a UTILIA OS:
POST /external/v1/tickets/ai-suggest
Pide sugerencias de IA para lo que escribe tu usuario.De UTILIA OS a Tu servidor:
jobId (respuesta)
El trabajo queda en cola.De Tu servidor a UTILIA OS:
GET /external/v1/tickets/ai-suggest/{jobId}/stream
Espera el resultado en tiempo real.De UTILIA OS a Tu servidor:
completed, con título, descripción, categoría y prioridad (respuesta)
Nota en UTILIA OS:
Tu equipo contesta el ticket desde Tickets de soporte.De UTILIA OS a Tu servidor:
Aviso TICKET_REPLIED
Llega a la dirección de avisos de tu aplicación.
Crea la aplicación y guarda su clave
Cada integración necesita su propia aplicación externa. Créala en Ajustes, Creadas por tu empresa con Nueva aplicación, marca «Tickets y soporte» en «¿Para qué la usarás?» y pulsa Crear la aplicación. Al terminar verás la clave de API, del tipo
utl_xxxxxxxxxxxxxxxx, que enviarás en cada petición:HTTPX-Api-Key: utl_xxxxxxxxxxxxxxxxLa clave es secreta: úsala desde tu servidor y no la subas nunca al repositorio. Si una web tuya tiene que llamar a la API desde el navegador, como el widget de soporte, usa otra aplicación con dominios autorizados y solo los permisos del navegador; lo explica Claves dentro de una página web. El alta paso a paso está en Panel de aplicaciones externas.
Concede los permisos
La aplicación nace sin permisos. En su ficha, Configuración › Ajustes, pestaña Permisos, aplica el conjunto «Tickets y soporte», que concede:
crm:users:readycrm:users:write, para listar y dar de alta usuarios.support:tickets:readysupport:tickets:write, para leer y abrir tickets.
Si vas a seguir los pasos de IA y de errores, añade a mano «IA en tickets de soporte» (
support:tickets:ai) y «Enviar errores de la aplicación» (analytics:errors:write).Identifica al usuario final
Antes de crear tickets, pedir IA o emitir facturas para un usuario, llama a
POST /users/identify. Es el paso que vincula tu usuario con UTILIA OS mediante unexternalIdque tú eliges.Casi todos los endpoints que reciben
userIden la dirección o como campo suelto del cuerpo dan por hecho que el usuario ya existe; si no, responden404conerrorCode: USER_NOT_FOUND. Las excepciones son los que aceptan un objetouser: { externalId, email, name }en el cuerpo, comoPOST /ticketsyPOST /invoices: crean el usuario si no existe y lo actualizan si ya existe.curl -X POST 'https://os.utilia.ai/api/external/v1/users/identify' \ -H 'Content-Type: application/json' \ -H 'X-Api-Key: utl_xxx' \ -d '{ "externalId": "user_42", "email": "ada@example.com", "name": "Ada Lovelace" }'Llama a
identifycuando el usuario inicia sesión en tu aplicación y otra vez si cambian su correo o su nombre. Detalle en Usuarios externos.Con el SDK de JavaScript o de Python, identifica al usuario de forma explícita con
sdk.users.identify(...)antes de operar sobre él: el SDK no lo hace por ti. Ver la configuración del SDK de JavaScript o del SDK de Python.Abre un ticket
Tienes dos formas equivalentes de crear el ticket. Elige la que mejor encaje con tu flujo:
Opción A: deja que el propio endpoint cree el usuario. No necesita
identifyprevio: UTILIA OS busca al usuario por la pareja aplicación másexternalIdy lo crea si no existe.const res = await fetch('https://os.utilia.ai/api/external/v1/tickets', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Api-Key': 'utl_xxx', }, body: JSON.stringify({ user: { externalId: 'user_42', email: 'ada@example.com', name: 'Ada Lovelace', }, title: 'No puedo subir archivos al panel', description: 'Recibo un timeout al intentar adjuntar el PDF de la factura.', category: 'BUG', priority: 'ALTA', context: { url: 'https://mi-app.com/facturas/123' }, }), }); const ticket = await res.json();Opción B: si ya llamaste a
/users/identify, envía solouser.externalId. Los datos que omitas no se sobrescriben.Todos los campos del ticket están en Endpoints básicos de tickets.
Pide sugerencias de IA
La IA analiza el texto, el audio o las imágenes que escribe tu usuario y propone
title,description,categoryyprioritypara el ticket. A diferencia dePOST /tickets,POST /tickets/ai-suggestno crea el usuario: eluserIddel cuerpo tiene que existir ya, o la respuesta es404conUSER_NOT_FOUND.// En tu servidor, con la clave: // 1. Solicitar sugerencias (devuelve un jobId) const enqueue = await fetch( 'https://os.utilia.ai/api/external/v1/tickets/ai-suggest', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Api-Key': 'utl_xxx', }, body: JSON.stringify({ userId: 'user_42', // debe estar previamente identificado text: 'No puedo subir archivos. Sale un timeout cuando adjunto el PDF.', existingFields: { title: '', description: '' }, source: 'SDK_EXTERNAL', }), }, ); const { jobId } = await enqueue.json(); // 2. Recibir el resultado en tiempo real (o consultando el estado). // EventSource no puede enviar cabeceras: tu servidor pide un pase de un // solo uso con la clave en la cabecera... const pase = await fetch('https://os.utilia.ai/api/external/v1/tickets/stream-token', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Api-Key': 'utl_xxx' }, body: JSON.stringify({ route: 'ai-suggest', jobId }), }).then((r) => r.json()); // ...y el navegador abre la conexión solo con el pase, nunca con la clave. const eventSource = new EventSource( `https://os.utilia.ai/api/external/v1/tickets/ai-suggest/${jobId}/stream?streamToken=${pase.streamToken}`, ); eventSource.onmessage = (msg) => { const data = JSON.parse(msg.data); if (data.event === 'completed') { console.log('Sugerencias:', data.result.suggestions); eventSource.close(); } };Opciones de audio e imágenes en Endpoints de sugerencias con IA.
Envía los errores de tu aplicación
Con el SDK, la captura son dos líneas:
sdk.errors.install()al arrancar, que recoge sola lo que nadie trata, ysdk.errors.capture(error)donde ya atrapas un error. Nunca lanzan y envían en lotes (Capturar los errores de tu aplicación).Sin el SDK, hay dos endpoints distintos, y confundirlos es de lo más habitual al integrar:
Endpoint Para qué sirve Quién lo llama Campos POST /errors/reportyPOST /errors/batchLos errores de tu aplicación, con contexto, uno a uno o en lotes de hasta 25 Tu servidor, o el SDK con installycapturemessageymodule(obligatorios), y ademásseverity,stack,file,line,function,endpoint,method,userId,requestId,context,release,environment,fingerprintytagsPOST /tickets/client-errorsLos fallos del propio widget y de ai.reportError, en lotes de hasta 10El widget y el SDK errors[]conmessage,stack,code,severity,endpoint,method,requestId,component,url,userAgent,releaseyenvironmentRegla práctica: los errores de tu aplicación van a
/errors/reporto/errors/batch;/tickets/client-errorses la telemetría ligera del widget. Detalle en Enviar los errores de tu aplicación y Telemetría ligera del widget y del SDK.Trata los errores en tu interfaz
Cuando algo falla, UTILIA OS responde siempre con la misma forma:
JSON{ "statusCode": 404, "message": "Usuario externo no encontrado. Usa /users/identify primero.", "error": "NOT_FOUND", "errorCode": "USER_NOT_FOUND", "timestamp": "2026-05-07T10:31:12.000Z", "requestId": "550e8400-e29b-41d4-a716-446655440000" }No pases el cuerpo del error a tu interfaz
error.response.dataes un objeto, no un texto. Si lo pintas contoast.error(error.response.data)o lo metes como hijo en JSX, React lanza «Objects are not valid as a React child» y la pantalla se rompe. Muestramessage, decide conerrorCodey guardarequestIdpara soporte.La receta completa está en Errores, identificador de petición y reintentos.
Recibe avisos en tu servidor
Para enterarte de lo que pasa sin preguntar (una respuesta en un ticket con
TICKET_REPLIED, su resolución conTICKET_RESOLVED, un usuario nuevo conUSER_CREATED), configura la dirección de avisos en Configuración › Avisos a tu servidor, en la ficha, y comprueba la firma de cada entrega. Es opcional, pero ahorra muchas consultas.Catálogo de eventos y firma en Avisos a tu servidor: visión general.
Errores típicos en la primera integración
| Síntoma | Causa más probable | Qué hacer |
|---|---|---|
403 INSUFFICIENT_SCOPE | La aplicación no tiene el permiso de esa operación; requiredScopes dice cuál | Concédelo en Configuración › Ajustes, pestaña Permisos, de la ficha de la aplicación |
404 USER_NOT_FOUND al llamar a ai-suggest u otros endpoints de tickets e IA con userId suelto | El usuario no se identificó antes | Llama a POST /users/identify y repite. Con el SDK, sdk.users.identify(...) |
404 EXTERNAL_USER_NOT_FOUND en invoices/stats, invoices/:id, invoices/:id/pay, payment-methods u otros endpoints de facturación | Lo mismo: los endpoints de facturación usan este código | Trátalo igual que USER_NOT_FOUND |
Faltan module, file o line en los errores registrados | Enviaste a /tickets/client-errors un error de tu aplicación: esos campos se descartan | Usa sdk.errors.capture o /errors/report |
| React: «Objects are not valid as a React child» tras un fallo de la API | Pintaste el objeto de error completo | Muestra message |
403 APP_DISABLED en cualquier endpoint | Alguien desactivó la aplicación | Reactívala desde su ficha (ver Panel de aplicaciones externas) |
429 RATE_LIMIT_EXCEEDED | Agotaste un cupo por hora: el de la aplicación o el propio de una ruta, como contactos o clientes. El mensaje dice cuál | Respeta Retry-After y reparte las llamadas; las cifras están en Límites de uso y CORS |
403 BILLING_NOT_ENABLED al emitir la primera factura | La aplicación tiene permisos de facturas, pero su facturación está apagada | Un Super Administrador la enciende con Activar la facturación, en Dinero › Facturación de la ficha de la aplicación. El Resumen y la pestaña Pruebas lo marcan como paso pendiente, y POST /external/v1/invoices/validate lo avisa sin emitir nada |
Siguiente paso
- Repasa Dónde va cada parámetro: es la causa número uno de errores
400y404en las primeras integraciones. - Si vas a emitir facturas, sigue la guía de facturación y cobro y, antes de la primera real, compruébalo todo sin emitir nada con Probar la facturación sin emitir facturas.
- Antes de pasar a producción, vigila tu integración desde la pestaña de métricas de su ficha: lo explica Métricas de la API.
Ponlo en práctica
Lo encontrarás en Creadas por tu empresa. Para abrirlo, entra con tu cuenta de UTILIA OS.