Tickets de soporte
Endpoints para crear, consultar, cerrar y reabrir tickets de soporte, añadirles mensajes y recibir sus cambios en tiempo real, con los permisos support:tickets:*.
Actualizado el . 11 min de lectura
Incluido en los planes Profesional y Avanzado. Ver precios
Con estos endpoints los usuarios de tu aplicación abren tickets de
soporte, envían mensajes, adjuntan archivos y reciben las respuestas de
tu equipo sin salir de tu aplicación. Todas las peticiones llevan la
cabecera X-Api-Key con una clave válida.
Plan y permisos
Antes que el permiso de la aplicación se comprueba el plan de tu
organización: hace falta el aplicativo Tickets de soporte con la
capacidad de tickets propios (support.own_tickets). Si falta, la
respuesta es 403 con MODULE_NOT_ENABLED o CAPABILITY_NOT_ENABLED.
| Endpoint | Permiso de la API (scope) |
|---|---|
POST /external/v1/tickets | support:tickets:write |
GET /external/v1/tickets | support:tickets:read |
GET /external/v1/tickets/:id | support:tickets:read |
GET /external/v1/tickets/unread-count | support:tickets:read |
GET /external/v1/tickets/events (SSE) | support:tickets:read |
POST /external/v1/tickets/:id/messages | support:tickets:write |
PATCH /external/v1/tickets/:id/close | support:tickets:write |
PATCH /external/v1/tickets/:id/reopen | support:tickets:write |
POST /external/v1/tickets/ai-suggest | support:tickets:ai |
POST /external/v1/tickets/ai-transcribe, ai-transcribe-async | support:tickets:ai |
Sin el permiso adecuado, los endpoints responden 403 INSUFFICIENT_SCOPE
con un mensaje genérico que pide hablar con el administrador de la
organización. El catálogo y las reglas de concesión están en
Permisos de la API.
No hay periodo de gracia
Versiones anteriores de esta página anunciaban catorce días de tolerancia para las aplicaciones dadas de alta antes del 25 de mayo de 2026. Esa tolerancia no se aplica: el permiso se exige en todas las llamadas desde el primer momento, también en aplicaciones antiguas. Concédelo en la ficha de la aplicación antes de integrar.
support:tickets:ai (sugerencias y transcripciones) es un permiso
sensible: consume cuota de IA y envía datos del usuario a un proveedor
externo. Solo lo concede un Super Administrador, con un motivo de al
menos 20 caracteres.
Resumen de endpoints
| Método | Ruta | Descripción |
|---|---|---|
POST | /api/external/v1/tickets | Crear un nuevo ticket |
GET | /api/external/v1/tickets | Listar tickets de un usuario |
GET | /api/external/v1/tickets/:id | Ver el detalle de un ticket |
GET | /api/external/v1/tickets/unread-count | Recuento de mensajes sin leer |
POST | /api/external/v1/tickets/:id/messages | Añadir un mensaje a un ticket |
PATCH | /api/external/v1/tickets/:id/close | Cerrar un ticket |
PATCH | /api/external/v1/tickets/:id/reopen | Reabrir un ticket |
GET | /api/external/v1/tickets/events | Cambios en tiempo real (SSE) |
Crear un ticket
/api/external/v1/ticketsCrea un ticket de soporte a nombre de un usuario externo. Permiso: support:tickets:write.
Encabezados requeridos
- X-Api-Keystringobligatorio
- Clave de API externa (formato: utl_xxx)
- Content-Typestringobligatorio
- application/json
- x-request-idstring
- Identificador único para seguir la petición
Cuerpo de la petición
- userExternalUserDtoobligatorio
- Datos del usuario que crea el ticket
- titlestringobligatorio
- Título del ticket (entre 5 y 500 caracteres)
- descriptionstringobligatorio
- Descripción detallada del problema (mínimo 10 caracteres)
- categorystring
- Categoría del ticket: BUG, CONSULTA o SUGERENCIA
- prioritystring
- Prioridad del ticket: BAJA, MEDIA, ALTA o CRITICA
- contextExternalContextDto
- Información del entorno donde se originó el ticket
- attachmentIdsstring[]
- Identificadores de archivos adjuntos ya subidos (ver Archivos)
Objeto user (ExternalUserDto)
- externalIdstringobligatorio
- Identificador único del usuario en tu sistema (máximo 255 caracteres)
- emailstring
- Correo electrónico del usuario (máximo 255 caracteres)
- namestring
- Nombre del usuario (máximo 255 caracteres)
- metadataobject
- Metadatos adicionales del usuario en formato clave-valor
Objeto context (ExternalContextDto)
- urlstring
- URL de la página donde se originó el ticket
- appVersionstring
- Versión de la aplicación del usuario
- browserInfostring
- Información del navegador (user agent)
- customDataobject
- Datos personalizados adicionales en formato clave-valor
Respuesta correcta (201 Created)
- idstring
- Identificador del ticket (UUID)
- ticketKeystring
- Clave única legible del ticket (ejemplo: APP-0001)
- titlestring
- Título del ticket
- statusstring
- Estado inicial del ticket (siempre OPEN)
- createdAtstring
- Fecha de creación en formato ISO 8601
Errores
| Código HTTP | Código de error | Descripción |
|---|---|---|
| 400 | VALIDATION_ERROR | El cuerpo de la petición no cumple las validaciones (título muy corto, descripción ausente, etc.) |
| 401 | -- | La clave de API es inválida o no fue proporcionada |
Ejemplos
curl -X POST 'https://os.utilia.ai/api/external/v1/tickets' \
-H 'Content-Type: application/json' \
-H 'X-Api-Key: utl_tu_clave_de_api' \
-H 'x-request-id: req-crear-ticket-001' \
-d '{
"user": {
"externalId": "usr-4821",
"email": "laura@ejemplo.com",
"name": "Laura García"
},
"title": "No puedo acceder a la sección de facturación",
"description": "Al intentar entrar en Facturación desde el menú lateral, la página muestra un error 500 y no carga ningún contenido.",
"category": "BUG",
"priority": "ALTA",
"context": {
"url": "https://mi-app.com/dashboard/facturación",
"appVersion": "2.4.1",
"browserInfo": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0"
}
}'user.email y user.name no son obligatorios, pero conviene enviarlos: tu equipo de soporte identificará antes a la persona.
Listar los tickets de un usuario
/api/external/v1/ticketsLista los tickets de un usuario externo, paginados y con filtros opcionales. Permiso: support:tickets:read.
Parámetros de consulta (query)
- userIdstringobligatorio
- Identificador externo del usuario. La API solo devuelve sus tickets.
- statusstring
- Filtra por estado: OPEN, IN_REVIEW, CONVERTED, RESOLVED o CLOSED
- pagenumberPor defecto:
1 - Número de página
- limitnumberPor defecto:
20 - Elementos por página (máximo 100)
Respuesta correcta (200 OK)
La respuesta tiene dos campos: data (la lista de tickets) y pagination (la paginación).
Campos de cada ticket en data
- idstring
- ID del ticket (UUID)
- ticketKeystring
- Clave única legible (ejemplo: APP-0012)
- titlestring
- Título del ticket
- categorystring
- Categoría: BUG, CONSULTA o SUGERENCIA
- prioritystring
- Prioridad: BAJA, MEDIA, ALTA o CRITICA
- statusstring
- Estado actual: OPEN, IN_REVIEW, CONVERTED, RESOLVED o CLOSED
- unreadByExternalnumber
- Cantidad de mensajes no leídos por el usuario externo
- createdAtstring
- Fecha de creación en formato ISO 8601
- lastActivityAtstring
- Fecha de la última actividad en el ticket
Campos de pagination
- pagenumber
- Página actual
- limitnumber
- Elementos por página
- totalnumber
- Total de tickets que coinciden con los filtros
- totalPagesnumber
- Número total de páginas
Ejemplos
curl -X GET 'https://os.utilia.ai/api/external/v1/tickets?userId=usr-4821&status=OPEN&page=1&limit=10' \
-H 'X-Api-Key: utl_tu_clave_de_api' \
-H 'x-request-id: req-listar-tickets-001'Ver un ticket
/api/external/v1/tickets/:idDetalle completo de un ticket, con mensajes, adjuntos y contexto. Permiso: support:tickets:read.
Parámetros de ruta
- idstringobligatorio
- ID del ticket (UUID)
Parámetros de consulta (query)
- userIdstringobligatorio
- Identificador externo del usuario. Se comprueba que el ticket sea suyo.
Respuesta correcta (200 OK)
- idstring
- ID del ticket (UUID)
- ticketKeystring
- Clave única legible (ejemplo: APP-0042)
- titlestring
- Título del ticket
- descriptionstring
- Descripción completa del problema
- categorystring
- Categoría del ticket
- prioritystring
- Prioridad del ticket
- statusstring
- Estado actual del ticket
- createdAtstring
- Fecha de creación en formato ISO 8601
- lastActivityAtstring
- Fecha de la última actividad
- messagesTicketMessage[]
- Lista de mensajes del ticket (ordenados cronológicamente)
- attachmentsTicketAttachment[]
- Adjuntos del ticket original
- contextobject | null
- Contexto del entorno capturado al crear el ticket
- reporterobject | null
- Datos del usuario que creó el ticket
Estructura de cada mensaje (TicketMessage)
- idstring
- ID del mensaje (UUID)
- contentstring
- Contenido del mensaje
- authorMessageAuthor
- Autor del mensaje
- attachmentsTicketAttachment[]
- Archivos adjuntos del mensaje
- createdAtstring
- Fecha de creación del mensaje
Estructura de author (MessageAuthor)
- typestring
- Tipo de autor: EXTERNAL (usuario) o INTERNAL (agente de soporte)
- idstring
- ID del autor
- namestring
- Nombre visible del autor
Estructura de cada adjunto (TicketAttachment)
- idstring
- ID del archivo
- filenamestring
- Nombre del archivo
- mimeTypestring
- Tipo MIME del archivo (ejemplo: image/png)
- sizenumber
- Tamaño del archivo en bytes
Errores
| Código HTTP | Código de error | Descripción |
|---|---|---|
| 404 | TICKET_NOT_FOUND | El ticket no existe o es de otra aplicación |
| 403 | TICKET_ACCESS_DENIED | El ticket no es de ese usuario |
Ejemplos
curl -X GET 'https://os.utilia.ai/api/external/v1/tickets/a1b2c3d4-e5f6-7890-abcd-ef1234567890?userId=usr-4821' \
-H 'X-Api-Key: utl_tu_clave_de_api' \
-H 'x-request-id: req-detalle-ticket-001'Mensajes sin leer
/api/external/v1/tickets/unread-countNúmero total de mensajes sin leer del usuario en todos sus tickets. Permiso: support:tickets:read.
Parámetros de consulta (query)
- userIdstringobligatorio
- ID externo del usuario
Respuesta correcta (200 OK)
- unreadCountnumber
- Cantidad total de mensajes sin leer del usuario en todos sus tickets
Ejemplos
curl -X GET 'https://os.utilia.ai/api/external/v1/tickets/unread-count?userId=usr-4821' \
-H 'X-Api-Key: utl_tu_clave_de_api' \
-H 'x-request-id: req-unread-001'Úsalo para mostrar en tu interfaz un indicador con el número de mensajes pendientes. Consúltalo al recibir un evento ticket-updated en lugar de preguntar cada poco.
Añadir un mensaje
/api/external/v1/tickets/:id/messagesAñade un mensaje a un ticket existente. Permiso: support:tickets:write.
Parámetros de ruta
- idstringobligatorio
- ID del ticket (UUID)
Parámetros de consulta (query)
- userIdstringobligatorio
- Identificador externo del usuario. Se comprueba que el ticket sea suyo.
Cuerpo de la petición
- contentstringobligatorio
- Contenido del mensaje (entre 1 y 5.000 caracteres)
- attachmentIdsstring[]
- Identificadores de archivos adjuntos ya subidos
Respuesta correcta (201 Created)
Devuelve el ticket actualizado, con el mensaje nuevo dentro de messages.
Errores
| Código HTTP | Código de error | Descripción |
|---|---|---|
| 404 | TICKET_NOT_FOUND | El ticket no existe o es de otra aplicación |
| 403 | TICKET_ACCESS_DENIED | El ticket no es de ese usuario |
| 403 | TICKET_CLOSED | No se pueden añadir mensajes a un ticket cerrado |
Ejemplos
curl -X POST 'https://os.utilia.ai/api/external/v1/tickets/a1b2c3d4-e5f6-7890-abcd-ef1234567890/messages?userId=usr-4821' \
-H 'Content-Type: application/json' \
-H 'X-Api-Key: utl_tu_clave_de_api' \
-H 'x-request-id: req-mensaje-001' \
-d '{
"content": "He probado a borrar la cache del navegador y el problema persiste. Adjunto una nueva captura de pantalla.",
"attachmentIds": ["f8e7d6c5-b4a3-2190-fedc-ba9876543210"]
}'Cerrar un ticket
/api/external/v1/tickets/:id/closeCierra un ticket. Permiso: support:tickets:write.
Parámetros de ruta
- idstringobligatorio
- ID del ticket (UUID)
Parámetros de consulta (query)
- userIdstringobligatorio
- Identificador externo del usuario. Se comprueba que el ticket sea suyo.
Respuesta correcta (200 OK)
Devuelve el ticket actualizado con status en CLOSED. Cerrar un ticket que ya estaba cerrado no da error: vuelve a quedar cerrado.
Errores
| Código HTTP | Código de error | Descripción |
|---|---|---|
| 404 | TICKET_NOT_FOUND | El ticket no existe o es de otra aplicación |
| 403 | TICKET_ACCESS_DENIED | El ticket no es de ese usuario |
Ejemplos
curl -X PATCH 'https://os.utilia.ai/api/external/v1/tickets/a1b2c3d4-e5f6-7890-abcd-ef1234567890/close?userId=usr-4821' \
-H 'X-Api-Key: utl_tu_clave_de_api' \
-H 'x-request-id: req-cerrar-ticket-001'Reabrir un ticket
/api/external/v1/tickets/:id/reopenReabre un ticket cerrado o resuelto. Permiso: support:tickets:write.
Parámetros de ruta
- idstringobligatorio
- ID del ticket (UUID)
Parámetros de consulta (query)
- userIdstringobligatorio
- Identificador externo del usuario. Se comprueba que el ticket sea suyo.
Respuesta correcta (200 OK)
Devuelve el ticket actualizado con status en OPEN.
Errores
| Código HTTP | Código de error | Descripción |
|---|---|---|
| 404 | TICKET_CANNOT_REOPEN | El ticket no existe o no está en CLOSED ni en RESOLVED |
| 403 | TICKET_ACCESS_DENIED | El ticket no es de ese usuario |
Ejemplos
curl -X PATCH 'https://os.utilia.ai/api/external/v1/tickets/a1b2c3d4-e5f6-7890-abcd-ef1234567890/reopen?userId=usr-4821' \
-H 'X-Api-Key: utl_tu_clave_de_api' \
-H 'x-request-id: req-reabrir-ticket-001'Solo se pueden reabrir tickets en CLOSED o RESOLVED. Con un ticket en OPEN, IN_REVIEW o CONVERTED, la respuesta es 404 con el código TICKET_CANNOT_REOPEN.
Cambios en tiempo real (SSE)
/api/external/v1/tickets/eventsConexión SSE (Server-Sent Events) para recibir en tiempo real los cambios de los tickets. Permiso: support:tickets:read.
Este endpoint abre una conexión persistente con Server-Sent Events (SSE). El servidor envía un evento cada vez que cambia algo relevante en los tickets de tu aplicación. Además de la cabecera X-Api-Key, admite la clave en el parámetro ?apiKey= para que funcione con EventSource, que no permite cabeceras propias.
Parámetros de consulta (query)
- userIdstring
- Identificador externo del usuario. Si lo indicas, solo llegan eventos de sus tickets.
Tipos de eventos
| Evento | Descripción | Datos |
|---|---|---|
connected | Conexión establecida | { appId: string } |
ticket-updated | Un ticket ha cambiado (mensaje nuevo, cambio de estado…) | { ticketId, ticketKey, type, externalUserId } |
heartbeat | Señal para mantener viva la conexión | Cada 30 segundos |
Ejemplos
curl -N 'https://os.utilia.ai/api/external/v1/tickets/events?userId=usr-4821' \
-H 'X-Api-Key: utl_tu_clave_de_api' \
-H 'x-request-id: req-sse-001'La conexión queda abierta sin límite de tiempo. Si se corta (por ejemplo, por un fallo de red), el cliente tiene que volver a conectarse; casi todas las implementaciones de EventSource lo hacen solas. En Python puedes usar la librería sseclient-py (pip install sseclient-py), y en Node.js, eventsource (npm install eventsource) si necesitas enviar cabeceras.
Estados del ticket
Estados de un ticket de soporte
Abierto
OPEN
Al crearlo o al reabrirlo desde tu aplicación.Se llega desde Abierto cuando: Tu equipo empieza a revisarlo
En revisión
IN_REVIEW
Lo cambia tu equipo desde UTILIA OS.Se llega desde En revisión cuando: Tu equipo lo convierte en un elemento de trabajo
Convertido
CONVERTED
Solo puede pasar a Resuelto o a Cerrado.
Se llega desde En revisión cuando: Tu equipo lo resuelve
Resuelto
RESOLVED
Se puede reabrir.Se llega desde Resuelto cuando: El usuario lo cierra con close o lo cierra tu equipo
Cerrado
CLOSED
Estado final
Se puede reabrir.
Otras transiciones
- Convertido pasa a Resuelto: Tu equipo lo da por resuelto.
- Resuelto pasa a Abierto: Tu aplicación lo reabre con reopen.
- Cerrado pasa a Abierto: Tu aplicación lo reabre con reopen.
Desde la API externa solo se puede cerrar (close, desde cualquier estado) y reabrir (reopen, desde CLOSED o RESOLVED, que vuelve a OPEN). El resto de cambios los hace tu equipo de soporte en UTILIA OS.
Errores frecuentes
Todos los endpoints de tickets pueden devolver estos errores:
| Código de error | HTTP | Descripción |
|---|---|---|
TICKET_NOT_FOUND | 404 | El ticket no existe o es de otra aplicación |
TICKET_ACCESS_DENIED | 403 | El ticket no es del usuario indicado |
TICKET_CLOSED | 403 | La operación no se permite en un ticket cerrado (por ejemplo, añadir mensajes) |
TICKET_CANNOT_REOPEN | 404 | El ticket no existe o no está cerrado ni resuelto, así que no se puede reabrir |
VALIDATION_ERROR | 400 | El cuerpo contiene datos no válidos (campos que faltan, valores fuera de rango…) |
MODULE_NOT_ENABLED / CAPABILITY_NOT_ENABLED | 403 | El plan de tu organización no incluye Tickets de soporte o los tickets propios |
| -- | 401 | Falta la clave de API en X-Api-Key, no es válida o ha caducado |
Formato de las respuestas de error
Todas las respuestas de error tienen la misma forma:
{
"statusCode": 404,
"error": "TICKET_NOT_FOUND",
"message": "El ticket solicitado no existe"
}Incluye siempre la cabecera x-request-id en tus peticiones: si tienes que escribir a soporte por un error inesperado, ese identificador permite localizar la petición exacta en los registros.
Siguiente paso
Los usuarios pueden adjuntar archivos a sus tickets con los endpoints de archivos, y tu aplicación puede proponer respuestas con las sugerencias con IA. Si prefieres no construir la interfaz, el widget de soporte ya la trae hecha.