Enviar los errores de tu aplicación
Endpoints para que tu aplicación envíe sus errores a UTILIA OS, uno a uno o en lotes de 25, con una respuesta que dice qué pasó con cada uno.
Actualizado el . 7 min de lectura
Incluido en todos los planes. Ver precios
Con estos endpoints tu aplicación envía sus errores a UTILIA OS. Los iguales se agrupan en uno solo con un contador, y la respuesta dice si se ha creado un grupo, si se ha sumado a uno que ya existía o por qué no se ha registrado. Tu equipo los ve en la pestaña Errores de la aplicación. Con los SDK no hace falta llamar a estos endpoints a mano: install y capture lo hacen por ti (Capturar los errores de tu aplicación).
Un error, de tu aplicación a tu equipo
Participan: Tu aplicación, UTILIA OS, Tu equipo.
De Tu aplicación a UTILIA OS:
POST /api/external/v1/errors/batch
De 1 a 25 errores con su mensaje, módulo, pila y contexto.Nota en UTILIA OS:
Agrupa cada error con los iguales y limpia el contexto.De UTILIA OS a Tu aplicación:
201 con el resultado de cada error (respuesta)
accepted, status o reason, y el hash del grupo.De UTILIA OS a Tu aplicación:
Aviso ERROR_REPORTED
Solo la primera vez que aparece un error.De UTILIA OS a Tu equipo:
El grupo aparece en la pestaña Errores
De UTILIA OS a Tu aplicación:
Aviso ERROR_REOPENED
Si un error resuelto vuelve a ocurrir, con reason regression.
Autenticación, plan y permisos
Todas las peticiones llevan la cabecera X-Api-Key, y el plan de tu organización tiene que incluir el aplicativo Integraciones.
| Endpoint | Permiso de la API (scope) |
|---|---|
POST /external/v1/errors/report | analytics:errors:write |
POST /external/v1/errors/batch | analytics:errors:write |
Sin el permiso, los endpoints responden 403 INSUFFICIENT_SCOPE con el permiso que falta en
requiredScopes; lo concede un administrador de la organización. Se exige desde la primera llamada, sin periodo de gracia.
Consultar, resolver y silenciar los errores tienen sus propios permisos: están en
Consultar y gestionar los errores. El catálogo completo y las reglas de
concesión están en Permisos de la API.
Límites
- 120 peticiones por minuto en cada ruta, por aplicación. Un lote de 25 cuenta como una petición. Al pasarlo,
429conRetry-After. Estas rutas no gastan el cupo por hora de tu clave. - 100 ocurrencias por minuto del mismo error. Las que pasan no se registran una a una, pero se suman al contador con la siguiente que entra.
- 200 errores distintos al día por aplicación, en hora UTC. Las ocurrencias de los errores que ya tienen grupo siempre entran.
- 14 días de conservación, contados desde la última ocurrencia. Un grupo sin resolver con un ticket o una tarea vinculados no caduca mientras siga así.
Enviar un error
/api/external/v1/errors/reportRegistra un error de tu aplicación. Permiso: analytics:errors:write.
Cuerpo
- messagestringobligatorio
- Mensaje del error, de 1 a 2000 caracteres.
- modulestringobligatorio
- Área de tu aplicación donde ocurrió (checkout, auth...), de 1 a 100 caracteres. Forma parte de la agrupación.
- severitystring
- critical, high, medium o low. Sin indicarla, low.
- stackstring
- Traza de la pila, hasta 10.000 caracteres. Su primer marco de tu código forma parte de la agrupación.
- filestring
- Archivo de origen, hasta 500 caracteres.
- linenumber
- Línea de origen (entero).
- functionstring
- Función de origen, hasta 200 caracteres.
- endpointstring
- Ruta de la petición que falló, hasta 500 caracteres.
- methodstring
- GET, POST, PUT, PATCH o DELETE.
- userIdstring
- Identificador de la persona afectada en tu sistema, hasta 255 caracteres. No se relaciona con los usuarios de UTILIA OS: se guarda en context.externalUserRef.
- requestIdstring
- Identificador de la petición, para cruzarlo con tus registros. Hasta 100 caracteres.
- contextobject
- Datos libres, hasta 64 KB en JSON (si pasa, 400). Al guardarse se quitan credenciales y correos y se recorta a 16 KB.
- releasestring
- Versión de tu aplicación, hasta 100 caracteres. Se guardan la primera y la última versión en que se vio el error.
- environmentstring
- Entorno (production, staging...), hasta 40 caracteres. Se guarda en minúsculas y no separa grupos.
- fingerprintstring[]
- Agrupación propia: de 1 a 8 textos de hasta 200 caracteres. Los errores del mismo módulo con la misma lista forman un grupo.
- tagsobject
- Hasta 20 etiquetas: nombres de hasta 50 caracteres y valores de texto (hasta 200), número o booleano. Se guardan en context.tags.
Agrupación. Sin fingerprint, dos errores son el mismo cuando coinciden el módulo, el mensaje normalizado (los identificadores largos, las fechas, los correos, las IP y la consulta de las direcciones se cambian por un comodín) y el primer marco de la pila que es de tu código. Dentro de fingerprint, el texto "{{default}}" inserta esa agrupación automática: ["{{default}}", "redsys"] separa el mismo fallo por pasarela. La gravedad no parte el grupo: guarda la más alta que ha visto.
Gravedad y errores esperados. Si context trae httpStatus, statusCode o errorCode con un código HTTP, decide la gravedad efectiva:
- 408, 502, 503 o 504 (un corte pasajero): se registra con gravedad
low, declares la que declares. - Cualquier otro 4xx: es un error de negocio o de permiso que tu aplicación ya trata, y no se registra (
accepted: false,reason: discarded_expected).
Para registrar un 4xx que sí importa, como una dependencia que te responde 403, pon el código en context.upstreamStatus.
Respuesta
201 con lo que ha pasado:
{
"success": true,
"accepted": true,
"status": "regressed",
"hash": "5f2b7c1e9a0d4e3b8c6a1f7d2e9b0c4a",
"deduplicated": true,
"count": 37
}- successboolean
- Siempre true: la petición se ha atendido.
- acceptedboolean
- true si el error ha quedado registrado.
- statusstring
- Solo con accepted. created: grupo nuevo; updated: ocurrencia sumada a un grupo existente; regressed: el grupo estaba resuelto y se ha reabierto solo.
- reasonstring
- Solo sin accepted. rate_limited, quota_exhausted, overloaded o discarded_expected.
- hashstring
- Identificador del grupo, el que aceptan las rutas /:hash. Vacío con discarded_expected.
- deduplicatedboolean
- true si el error se ha sumado a un grupo que ya existía (updated o regressed).
- countnumber
- Solo con accepted: ocurrencias acumuladas del grupo.
Ningún reason es un fallo de tu aplicación, y ninguno se arregla repitiendo:
reason | Qué significa |
|---|---|
rate_limited | Más de 100 ocurrencias por minuto del mismo error. Se suman al contador con la siguiente que entra. |
quota_exhausted | Tu aplicación ya ha registrado hoy 200 errores distintos. Los ya conocidos siguen entrando. |
overloaded | La plataforma se está protegiendo de una avalancha de errores. |
discarded_expected | El contexto trae un 4xx que no es un corte pasajero. |
Si UTILIA OS no puede guardar el error, la respuesta es 503 con la cabecera Retry-After: 30. Es el único caso que se repite: espera esos segundos y vuelve a enviarlo.
{
"statusCode": 503,
"errorCode": "ERROR_STORAGE_UNAVAILABLE",
"message": "No se ha podido guardar el error en este momento. Vuelve a enviarlo dentro de 30 segundos.",
"retryable": true
}Ejemplos
curl -X POST 'https://os.utilia.ai/api/external/v1/errors/report' \
-H 'Content-Type: application/json' \
-H 'X-Api-Key: utl_xxx' \
-d '{
"message": "Tiempo de espera agotado con la pasarela",
"module": "pagos",
"severity": "critical",
"endpoint": "/api/pagos/cobrar",
"method": "POST",
"release": "2.4.1",
"environment": "production",
"tags": { "pasarela": "redsys", "reintento": 2 },
"context": { "pedidoId": "ped_456" }
}'Enviar varios en un lote
/api/external/v1/errors/batchRegistra de 1 a 25 errores con el mismo contrato que /report, en orden. Permiso: analytics:errors:write.
El cuerpo es { "errors": [...] }, con de 1 a 25 errores que siguen exactamente el contrato de POST /report. Si alguno no es válido, el lote entero responde 400 y no se registra ninguno. El lote cuenta como una sola petición en el límite por minuto.
La respuesta, 201, trae el resultado de cada error en el orden en que los enviaste:
{
"received": 3,
"accepted": 1,
"results": [
{ "index": 0, "accepted": true, "status": "created", "hash": "5f2b7c1e9a0d4e3b8c6a1f7d2e9b0c4a" },
{ "index": 1, "accepted": false, "reason": "discarded_expected", "hash": "" },
{ "index": 2, "accepted": false, "reason": "storage_failed", "hash": "" }
]
}Además de los motivos de /report, un elemento puede traer reason: storage_failed: UTILIA OS no pudo guardarlo. El lote nunca responde 503; repite solo los elementos con storage_failed, nunca el lote entero, porque contarías dos veces los que sí se guardaron.
curl -X POST 'https://os.utilia.ai/api/external/v1/errors/batch' \
-H 'Content-Type: application/json' \
-H 'X-Api-Key: utl_xxx' \
-d '{
"errors": [
{ "message": "Factura sin cliente", "module": "importador", "release": "2.4.1" },
{ "message": "Fecha fuera de rango", "module": "importador", "release": "2.4.1" }
]
}'Con los SDK, sdk.errors.reportBatch(errores) (report_batch) hace un solo intento y devuelve este mismo resultado.
Errores
| HTTP | Cuándo |
|---|---|
400 | Cuerpo no válido: un campo pasa de su tamaño, context supera 64 KB, etiquetas mal formadas o un lote vacío o de más de 25. |
401 | Falta la clave de API o no es válida. |
403 | Falta analytics:errors:write (INSUFFICIENT_SCOPE), el plan no incluye Integraciones (MODULE_NOT_ENABLED), la aplicación está desactivada o el origen no está entre sus dominios autorizados. |
429 | Más de 120 peticiones por minuto en la ruta. Respeta Retry-After. |
503 | Solo en /report: ERROR_STORAGE_UNAVAILABLE. Repite pasados los segundos de Retry-After. |
Envía x-request-id en cada petición para poder seguirla en los registros.