Ir al contenido

API de eventos de auditoría

Probara expone dos endpoints de solo lectura para listar auditoría:

EndpointAlcanceRol mínimo
GET /api/v1/orgs/{orgUlid}/audit-eventsToda la organizaciónOwner o admin
GET /api/v1/runs/{runUlid}/audit-eventsUna ejecución concretaCualquier miembro que pueda leer el run (viewer+)

Ambos devuelven el mismo sobre AuditEventOut con paginación por cursor. Los eventos se ordenan por ULID descendente (más recientes primero).

GET /api/v1/orgs/{orgUlid}/audit-events devuelve el registro append-only de auditoría de una organización.

  • Sesión: inicia sesión en el navegador y envía la cookie de sesión más X-Organization-Id si llamas desde scripts propios.
  • Bearer: token de API con alcance de organización y membresía owner o admin en esa org.

Los miembros y visualizadores reciben 403 Forbidden.

ParámetroTipoDescripción
limitenteroTamaño de página. Por defecto 50. Valores mayores a 100 se limitan silenciosamente a 100.
cursorULIDSi está presente, devuelve eventos con ulid < cursor (página más antigua).
{
"events": [
{
"ulid": "01J…",
"action": "project.created",
"actor": {
"kind": "user",
"ulid": "01J…",
"email": "alice@example.com",
"displayName": null
},
"entityType": "project",
"entityUlid": "01J…",
"entityLabel": "Acme Web",
"runUlid": null,
"metadata": {},
"createdAt": 1700000000000
}
],
"nextCursor": "01J…"
}

nextCursor es el ULID del último evento en events cuando hay otra página; si no, null.

runUlid aparece en eventos ligados a un run (y en el listado org-wide cuando el run padre sigue existiendo). Los eventos que no son de run omiten el campo o lo envían como null.

GET /api/v1/runs/{runUlid}/audit-events devuelve solo los eventos cuyo run_id corresponde al run resuelto dentro de la organización activa. Usa la misma cookie de sesión o token bearer que el resto de APIs de runs; en sesión de navegador envía X-Organization-Id.

La autorización coincide con GET /api/v1/runs/{runUlid}: cualquier miembro de la organización con acceso al run puede listar su historial. No hay restricción owner/admin en esta ruta.

Los parámetros de consulta y el sobre de respuesta son idénticos al endpoint org-wide (limit, cursor, events, nextCursor). Un 404 indica que el ULID del run no existe en la organización activa.

Ejemplo:

Ventana de terminal
export PROBARA_API_TOKEN="probara_…"
export RUN_ULID="01J…"
curl -sS \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
"https://probara.net/api/v1/runs/${RUN_ULID}/audit-events?limit=50"
{ "kind": "api_token", "ulid": "01J…", "name": "CI deploy bot" }
{ "kind": "system" }

Si un usuario o token fue eliminado, los campos de referencia son null pero el evento sigue en la lista:

{ "kind": "user", "ulid": null, "email": null, "displayName": null }
Ventana de terminal
export PROBARA_API_TOKEN="probara_…"
export ORG_ULID="01J…"
curl -sS \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
"https://probara.net/api/v1/orgs/${ORG_ULID}/audit-events?limit=50"

Página siguiente:

Ventana de terminal
curl -sS \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
"https://probara.net/api/v1/orgs/${ORG_ULID}/audit-events?cursor=${NEXT_CURSOR}"

metadata es un objeto que puede incluir buckets de diff compartidos (documentados en OpenAPI como AuditEventDiff, AuditAssigneeChange y AuditRunAggregates) más claves de snapshot propias de cada acción. Los integradores DEBEN tratar claves desconocidas como compatibles hacia adelante.

Buckets de diff habituales:

ClaveUsada porForma
changes*.updated, run.case_marked, run.case_step_marked, run.case_retried, run.case_step_actual_result_set, run.result_attachment_committed (cuando también cambió el texto)AuditFieldChange[]from / to escalares con fromLabel / toLabel opcionales en referencias
assigneeChangerun.case_assigned{ from: { ulid, name } | null, to: { ulid, name } | null, via: 'open' | 'patch' | 'reconcile' }
bulkCasesrun.cases_bulk_*{ cases: AuditBulkCaseEntry[], omittedCount?, summary: { affected, unchanged, statusFrom?, statusTo?, assigneeFrom?, assigneeTo? } } — hasta 20 entradas por caso al emitir; el resto en omittedCount
runAggregatesrun.closed, run.aborted{ countPassed, countFailed, countBlocked, countSkipped, countUntested, totalDurationMs, projectedStatus: 'passed' | 'failed' | 'aborted' }run.aborted siempre reporta projectedStatus: 'aborted', aunque los conteos de casos por sí solos leerían 'passed'/'failed'

Los snapshots de texto mayores a 240 grafemas se recortan al emitir; los objetos afectados marcan truncated: true según la convención de auditoría.

entityTypeEtiqueta canónica
test_runNombre del run en el momento de emisión
test_run_case${caseDisplayId} · ${titleSnapshot}caseDisplayId es ${projectCode}-${caseNumber} (p. ej. ACME-1)
test_run_case_step${caseDisplayId} · ${titleSnapshot} — ${stepActionSnapshot ?? `paso ${stepPosition}`}

La lista puede añadir metadatos de enlace (runUlid, projectCode, caseDisplayId, titleSnapshot, runName) cuando la fila guardada no los tenía; los snapshots almacenados siempre prevalecen.

AcciónClaves relevantes en metadata
run.createdcaseCount, environment, defaultAssignee, milestone opcionales
run.updatedchanges (name, description, defaultAssigneeUlid, environment, milestoneUlid)
run.closedrunAggregates (sustituye el status plano heredado)
run.abortedrunAggregates (misma forma que run.closed; projectedStatus siempre es 'aborted')
run.deletedrunAggregates, wasState (open | closed), closedAt (null si seguía abierto)
run.clonedcaseCount, sourceRun, cloneAssignees, statusFilter (null si se clonaron todos los casos)
run.case_addedadded, cases (primeros 20), omittedCount opcional
run.case_markedcaseDisplayId, titleSnapshot, changes (status), elapsedMs opcional
run.case_step_markedcaseDisplayId, titleSnapshot, stepUlid, stepPosition, stepActionSnapshot, changes (status), caseStatus
run.case_assignedcaseDisplayId, titleSnapshot, assigneeChange
run.case_retriedcaseDisplayId, titleSnapshot, changes (status → untested), previousElapsedMs opcional
run.case_removedcaseDisplayId, titleSnapshot, statusAtRemoval
run.cases_bulk_markedbulkCases (por caso fromStatus / toStatus; summary.statusFrom / statusTo homogéneos cuando aplica)
run.cases_bulk_assignedbulkCases (por caso fromAssignee / toAssignee; resumen homogéneo cuando aplica)
run.cases_bulk_retriedbulkCases (transiciones por caso; summary.unchanged para casos ya sin probar)
run.cases_bulk_removedbulkCases (por caso statusAtRemoval)
run.case_step_actual_result_setcaseDisplayId, titleSnapshot, stepUlid, stepPosition, stepActionSnapshot, changes (actual_result de → a)
run.result_attachment_committedcaseDisplayId, titleSnapshot, stepUlid, stepPosition, attachments; changes opcional cuando el texto de actual_result cambió en la misma petición
run.result_attachment_deletedMisma forma que run.result_attachment_committed

run.reopened y run.reran_failed siguen siendo valores válidos del enum action para que las filas históricas sigan analizándose, pero ya no se emiten — Reopen se eliminó y rerun-failed se generalizó en run.cloned (POST .../runs/{runUlid}/clone).

Ejemplo — cierre con scorecard:

{
"action": "run.closed",
"entityType": "test_run",
"entityLabel": "Sprint 24",
"metadata": {
"runAggregates": {
"countPassed": 7,
"countFailed": 2,
"countBlocked": 1,
"countSkipped": 0,
"countUntested": 0,
"totalDurationMs": 935000,
"projectedStatus": "failed"
}
}
}

Ejemplo — reasignación de caso:

{
"action": "run.case_assigned",
"entityType": "test_run_case",
"entityLabel": "ACME-1 · Flujo de login",
"metadata": {
"caseDisplayId": "ACME-1",
"titleSnapshot": "Flujo de login",
"assigneeChange": {
"from": { "ulid": "01J…", "name": "María López" },
"to": { "ulid": "01J…", "name": "Pedro García" },
"via": "patch"
}
}
}

Mutaciones de ciclo de vida del run (abortar, clonar y eliminar)

Sección titulada «Mutaciones de ciclo de vida del run (abortar, clonar y eliminar)»

Estos endpoints mutan runs y emiten las acciones de auditoría documentadas arriba. No forman parte de las rutas de listado de auditoría.

  • Rol: miembro, admin u owner (viewer403)
  • Cuerpo: ninguno
  • Respuesta: 200 con el RunResponse actualizado (state: "closed", abortedAt fijado al momento del abandono)
  • Errores: 409 si el run no está actualmente abierto (ya cerrado o ya abortado)
  • Auditoría: run.aborted con metadata.runAggregates (projectedStatus siempre 'aborted')
  • Los casos sin probar quedan sin probar — abortar no registra resultados de caso. Esto es terminal: un run abortado permanece congelado (las mutaciones de caso devuelven 409); no existe reapertura. Usa .../clone (abajo) para iniciar un run nuevo a partir de él.

POST /api/v1/projects/{projectId}/runs/{runUlid}/clone

Sección titulada «POST /api/v1/projects/{projectId}/runs/{runUlid}/clone»

“Volver a ejecutar”: generaliza el antiguo endpoint rerun-failed (eliminado).

  • Rol: miembro, admin u owner (viewer403)
  • Cuerpo: { "title": "string, obligatorio, máx 200", "cloneAssignees"?: boolean, "statusFilter"?: TestOutcomeStatus[] }statusFilter selecciona qué casos del origen se copian; omitido o vacío significa todos.
  • Respuesta: 201 con el nuevo RunResponse (state: "open")
  • Errores: 422 si title está vacío o statusFilter contiene un estado desconocido
  • Auditoría: run.cloned con metadata.caseCount, metadata.sourceRun, metadata.cloneAssignees, metadata.statusFilter
  • Funciona sobre cualquier run origen cerrado — completado o abortado. Cuando cloneAssignees es true, cada caso copiado conserva el asignado del caso origen; si no, todos los casos del nuevo run empiezan sin asignar.
  • Rol: solo admin u owner (member / viewer403)
  • Respuesta: 204 No Content
  • Auditoría: run.deleted con metadata.runAggregates, wasState y closedAt capturados antes de la cascada. Tras el borrado, run_id en filas de auditoría (incluida esta) pasa a null por cascada FK; el evento sigue visible en listados de toda la organización.

Los endpoints de listado de auditoría son de solo lectura. Usa las rutas de run anteriores (y el resto de APIs del producto) para generar nuevas filas de auditoría.

Estas acciones se emiten para los comentarios en hilos de las páginas de entidades, incluyendo respuestas (parentUlid presente) y fijado/desfijado. Las acciones commented, comment_updated y comment_deleted incluyen metadata.commentUlid y metadata.excerpt (primeros 240 caracteres del cuerpo del comentario); comment_pinned y comment_unpinned incluyen solo metadata.commentUlid (fijar no lleva extracto del cuerpo).

AcciónSe emite cuandoTipo de entidad
defect.commentedSe agrega un comentario o respuesta a un defectodefect
defect.comment_updatedSe edita un comentario en un defectodefect
defect.comment_deletedSe elimina un comentario en un defecto (una vez por cada respuesta en cascada también)defect
defect.comment_pinnedSe fija un comentario de primer nivel en un defectodefect
defect.comment_unpinnedSe desfija un comentario de primer nivel en un defectodefect
test_case.commentedSe agrega un comentario o respuesta a un caso de pruebatest_case
test_case.comment_updatedSe edita un comentario en un caso de pruebatest_case
test_case.comment_deletedSe elimina un comentario en un caso de prueba (una vez por cada respuesta en cascada también)test_case
test_case.comment_pinnedSe fija un comentario de primer nivel en un caso de pruebatest_case
test_case.comment_unpinnedSe desfija un comentario de primer nivel en un caso de pruebatest_case

Ejemplo:

{
"action": "test_case.commented",
"entityType": "test_case",
"entityLabel": "TC-1 Login con credenciales válidas",
"metadata": {
"commentUlid": "01J…",
"excerpt": "Reproducido en staging con sesión nueva."
}
}

Crea y revoca tokens de API en Autenticación API (en la app: /workspace/api-tokens).