Errores frecuentes al integrar
Los diez errores que más se repiten en las primeras integraciones con la API externa de UTILIA OS y cómo evitarlos.
Actualizado el . 5 min de lectura
Incluido en todos los planes. Ver precios
Esta página recoge los errores que más se repiten en las primeras integraciones, cada uno con su forma correcta. Léela antes de llevar tu integración a producción.
1. No mandes userId en el cuerpo cuando el endpoint lo espera en la consulta
Muchos endpoints de tickets, facturas y tarjetas esperan userId en la consulta, no en el cuerpo. Mandarlo mal no da un error claro: la API descarta el campo que no espera y la petición falla después con USER_NOT_FOUND. La única pista es la cabecera X-Utilia-Ignored-Fields de la respuesta, que en las escrituras nombra los campos descartados; mírala en tus registros o comprueba la petición antes con POST /api/external/v1/diagnostics/check-request (Comprobar una petición sin ejecutarla).
Dónde aplica:
GET /tickets/{id}(query)GET /tickets(query)GET /tickets/unread-count(query)POST /tickets/{id}/messages(en la consulta, no en el cuerpo)PATCH /tickets/{id}/close(query)PATCH /tickets/{id}/reopen(query)GET /invoices/{id}(query)GET /invoices/{id}/pdf(query)DELETE /payment-methods/{pmId}(query)
Mal:
POST /api/external/v1/tickets/abc-123/messages
Content-Type: application/json
{ "userId": "usr-4821", "content": "..." }Bien:
POST /api/external/v1/tickets/abc-123/messages?userId=usr-4821
Content-Type: application/json
{ "content": "..." }La lista completa está en Dónde va cada parámetro.
2. No mandes campos extra dentro de context
context (en POST /tickets) admite solo url, appVersion, browserInfo y customData. Cualquier otro campo se descarta sin error: crees que envías datos que en realidad se pierden, y solo la cabecera X-Utilia-Ignored-Fields de la respuesta lo dice (por ejemplo, context.plan). Si necesitas más, mételos dentro de context.customData.
Mal:
{
"user": { "externalId": "usr-1" },
"title": "...",
"description": "...",
"context": {
"url": "https://miapp.com",
"userPlan": "premium",
"abVariant": "B"
}
}userPlan y abVariant no son campos de context: la petición se acepta, pero nunca llegan al ticket.
Bien:
{
"user": { "externalId": "usr-1" },
"title": "...",
"description": "...",
"context": {
"url": "https://miapp.com",
"customData": {
"userPlan": "premium",
"abVariant": "B"
}
}
}Si los datos son del usuario y no del momento del ticket, usa user.metadata, que se guarda con el usuario, en lugar de context.customData, que solo vale para ese ticket.
3. No pintes el objeto de error en tu interfaz
Cuando un endpoint falla, la respuesta es un objeto JSON con la forma:
{
"statusCode": 400,
"message": "Tipo de archivo no permitido: image/bmp...",
"error": "BAD_REQUEST",
"errorCode": "FILE_INVALID_MIME",
"timestamp": "2026-02-10T14:23:45.123Z",
"requestId": "550e8400-..."
}Si pasas el objeto entero como hijo en JSX (<div>{error.response.data}</div>) o a toast.error(error.response.data), React lanza «Objects are not valid as a React child» y la pantalla se rompe.
Mal:
toast.error(error.response.data); // React falla y la pantalla se rompeBien:
toast.error(error.response.data?.message ?? 'Error desconocido');
// y guarda error.response.data?.requestId para soporteUsa errorCode para decidir en tu código y message para el texto que ve la persona.
4. No confundas /errors/report con /tickets/client-errors
Son dos endpoints con propósitos distintos, y se confunden porque los dos reciben errores.
| Endpoint | Para qué sirve |
|---|---|
POST /errors/report y POST /errors/batch | Los errores de tu aplicación, en el navegador o en tu servidor. Aceptan module, file, line, function, context, release, fingerprint y tags, agrupan los iguales y responden por cada error. Es lo que usan install y capture del SDK. |
POST /tickets/client-errors | La telemetría ligera del widget y de ai.reportError. Acepta code, component, url y userAgent, en lotes de 10, y solo responde con recuentos. |
Si mandas un error de tu aplicación a /tickets/client-errors, pierdes module, file, line y context. Al revés, si mandas un fallo del widget a /errors/report, component, url y userAgent se descartan sin aviso; ponlos en context.
Lo más sencillo es no llamar a ninguno a mano: sdk.errors.install() y sdk.errors.capture(error) van al registro de errores, envían en lote y nunca lanzan. Detalle en Capturar los errores de tu aplicación y Telemetría ligera del widget y del SDK.
5. No mandes cuerpo a PATCH /close ni a /reopen
Estos endpoints solo usan el identificador de la ruta ({id}) y userId en la consulta. Si envías cuerpo, se ignora: los datos no se guardan y tampoco hay error.
Si quieres dejar el motivo del cierre, envíalo como mensaje antes de cerrar (ver el ejemplo).
6. No ignores USER_NOT_FOUND
404 USER_NOT_FOUND significa que el endpoint necesita un usuario externo ya identificado y no lo está. Pasa en casi todos los endpoints que reciben userId suelto (no un objeto user).
Mal: capturar el error y mostrar «Algo salió mal» sin hacer nada más.
Bien:
- Llama a
POST /users/identifyantes del primer endpoint que lo necesite y guarda elexternalIden tu sistema. - Con el SDK de JavaScript o Python, identifica al usuario con
sdk.users.identify(...)antes de operar sobre él. El SDK no lo hace por su cuenta:
const sdk = new UtiliaSDK({
baseURL: 'https://os.utilia.ai/api',
apiKey: 'utl_xxx',
});
await sdk.users.identify({
externalId: 'user_42',
email: 'ada@example.com',
name: 'Ada Lovelace',
});Más en la configuración del SDK de JavaScript y del SDK de Python.
7. No reintentes en errores 4xx
Reintenta solo en 429, 5xx o fallos de red pasajeros (tiempo agotado, conexión cortada), o cuando el cuerpo del error trae retryable: true (por ejemplo, un 409 porque la factura todavía se está emitiendo): entonces espera retryAfterSeconds y repite la misma petición. Reintentar un 400, 401, 403 o 404 no arregla nada y gasta tu cupo de peticiones, y un error con retryable: false tampoco se arregla repitiéndolo.
Usa espera exponencial (1, 2, 4 y 8 segundos) con algo de azar y un tope de cuatro intentos. Ante un 5xx o un corte de red en una escritura, mira antes si lleva idempotencyKey: con ella, repite con la misma; sin ella (abrir un ticket, identificar a un usuario), comprueba si la operación llegó a hacerse antes de repetirla. Qué rutas la admiten está en Repetir sin duplicar.
8. No pongas la clave de servidor en el navegador
La clave de una aplicación con permisos de servidor (facturas, cobros, CRM) es secreta: si llamas con ella desde el navegador, cualquiera puede verla en las herramientas de desarrollo. Llama desde tu servidor o pasa por un intermediario tuyo que añada la clave. Si tu web necesita llamar a la API desde el navegador, como hace el widget de soporte, usa otra aplicación, con dominios autorizados y solo los permisos del navegador: ver Claves dentro de una página web.
9. Envía tu propio X-Request-Id
La cabecera X-Request-Id une tu petición con los registros de UTILIA OS. Si no la envías, el servidor genera una y te la devuelve; si la envías tú, puedes seguir la misma petición en tus registros desde el principio. Genera un identificador por petición, guárdalo en tus registros y dáselo a soporte cuando algo falle.
X-Api-Key: utl_xxx
X-Request-Id: 7c2d5b30-f8a1-4d6e-9c3b-1234567890ab10. No decidas con el campo error
error ("BAD_REQUEST", "NOT_FOUND"...) solo repite el estado HTTP. Para decidir en tu código, usa errorCode ("USER_NOT_FOUND", "TICKET_CANNOT_REOPEN"...). El catálogo completo está en Errores, identificador de petición y reintentos.