Skip to content

Audit events API

Probara exposes two read-only audit listing endpoints:

EndpointScopeMinimum role
GET /api/v1/orgs/{orgUlid}/audit-eventsEntire organizationOwner or admin
GET /api/v1/runs/{runUlid}/audit-eventsSingle test runAny member who can read the run (viewer+)

Both return the same AuditEventOut envelope with cursor pagination. Events are ordered by ULID descending (newest first).

GET /api/v1/orgs/{orgUlid}/audit-events returns the append-only audit log for an organization.

  • Session: sign in in the browser and send the session cookie plus X-Organization-Id when calling from custom scripts.
  • Bearer token: organization-scoped API token with owner or admin membership on that org.

Members and viewers receive 403 Forbidden.

ParameterTypeDescription
limitintegerPage size. Default 50. Values above 100 are silently capped to 100.
cursorULIDWhen set, returns events with ulid < cursor (older page).
{
"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 is the ULID of the last event in events when another page exists; otherwise null.

runUlid is present on run-scoped events (and on org-wide listings when the parent run still exists). Non-run events omit the field or set it to null.

GET /api/v1/runs/{runUlid}/audit-events returns only events whose run_id matches the resolved run inside the active organization. Use the same session cookie or bearer token as other run APIs; send X-Organization-Id for browser sessions.

Authorization matches GET /api/v1/runs/{runUlid} — any organization member with access to the run may list its history. There is no owner/admin gate on this route.

Query parameters and the response envelope are identical to the organization-wide endpoint (limit, cursor, events, nextCursor). A 404 means the run ULID does not exist in the active organization.

Example:

Terminal window
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" }

When a user or token was deleted, reference fields are null but the event remains in the list:

{ "kind": "user", "ulid": null, "email": null, "displayName": null }
Terminal window
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"

Next page:

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

metadata is an object that may include shared diff buckets (documented in the OpenAPI components AuditEventDiff, AuditAssigneeChange, and AuditRunAggregates) plus action-specific snapshot keys. Integrators SHOULD treat unknown keys as forward-compatible.

Common diff buckets:

KeyUsed byShape
changes*.updated, run.case_marked, run.case_step_marked, run.case_retried, run.case_step_actual_result_set, run.result_attachment_committed (when text also changed)AuditFieldChange[] — scalar from / to with optional fromLabel / toLabel for reference fields
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? } } — up to 20 per-case entries at emission; remainder in omittedCount
runAggregatesrun.closed, run.aborted{ countPassed, countFailed, countBlocked, countSkipped, countUntested, totalDurationMs, projectedStatus: 'passed' | 'failed' | 'aborted' }run.aborted always reports projectedStatus: 'aborted', even when the case counts alone would read as 'passed'/'failed'

String snapshots longer than 240 graphemes are clipped at emission time; affected objects set truncated: true per the shared audit convention.

entityTypeCanonical label
test_runRun name at emission time
test_run_case${caseDisplayId} · ${titleSnapshot}caseDisplayId is ${projectCode}-${caseNumber} (e.g. ACME-1)
test_run_case_step${caseDisplayId} · ${titleSnapshot} — ${stepActionSnapshot ?? `step ${stepPosition}`}

List responses may add link metadata (runUlid, projectCode, caseDisplayId, titleSnapshot, runName) when the stored row omitted them; stored snapshots always win.

ActionNotable metadata keys
run.createdcaseCount, optional environment, defaultAssignee, milestone
run.updatedchanges (name, description, defaultAssigneeUlid, environment, milestoneUlid)
run.closedrunAggregates (replaces legacy flat status)
run.abortedrunAggregates (same shape as run.closed; projectedStatus is always 'aborted')
run.deletedrunAggregates, wasState (open | closed), closedAt (null when still open)
run.clonedcaseCount, sourceRun, cloneAssignees, statusFilter (null when every case was cloned)
run.case_addedadded, cases (first 20), optional omittedCount
run.case_markedcaseDisplayId, titleSnapshot, changes (status), optional elapsedMs
run.case_step_markedcaseDisplayId, titleSnapshot, stepUlid, stepPosition, stepActionSnapshot, changes (status), caseStatus
run.case_assignedcaseDisplayId, titleSnapshot, assigneeChange
run.case_retriedcaseDisplayId, titleSnapshot, changes (status → untested), optional previousElapsedMs
run.case_removedcaseDisplayId, titleSnapshot, statusAtRemoval
run.cases_bulk_markedbulkCases (per-case fromStatus / toStatus; homogeneous summary.statusFrom / statusTo when applicable)
run.cases_bulk_assignedbulkCases (per-case fromAssignee / toAssignee; homogeneous assignee summary when applicable)
run.cases_bulk_retriedbulkCases (per-case retry transitions; summary.unchanged for cases already untested)
run.cases_bulk_removedbulkCases (per-case statusAtRemoval)
run.case_step_actual_result_setcaseDisplayId, titleSnapshot, stepUlid, stepPosition, stepActionSnapshot, changes (actual_result from → to)
run.result_attachment_committedcaseDisplayId, titleSnapshot, stepUlid, stepPosition, attachments; optional changes when actual_result text changed in the same request
run.result_attachment_deletedSame shape as run.result_attachment_committed

run.reopened and run.reran_failed remain valid values of the action enum so historical rows keep parsing, but neither is emitted anymore — Reopen was removed and rerun-failed was generalized into run.cloned (POST .../runs/{runUlid}/clone).

Example — close 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"
}
}
}

Example — case reassignment:

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

Run lifecycle mutations (abort, clone, and delete)

Section titled “Run lifecycle mutations (abort, clone, and delete)”

These endpoints mutate runs and emit the audit actions documented above. They are not on the audit listing routes themselves.

  • Role: member, admin, or owner (viewer403)
  • Body: none
  • Response: 200 with the updated RunResponse (state: "closed", abortedAt set to the abort time)
  • Errors: 409 when the run is not currently open (already closed or already aborted)
  • Audit: run.aborted with metadata.runAggregates (projectedStatus always 'aborted')
  • Untested cases are left untested — abort records no case results. This is terminal: an aborted run stays frozen (case mutations return 409); there is no reopen. Use .../clone (below) to start a fresh run from it.

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

Section titled “POST /api/v1/projects/{projectId}/runs/{runUlid}/clone”

“Run again”: generalizes the former rerun-failed endpoint (removed).

  • Role: member, admin, or owner (viewer403)
  • Body: { "title": "string, required, max 200", "cloneAssignees"?: boolean, "statusFilter"?: TestOutcomeStatus[] }statusFilter selects which source cases carry over; omitted or empty means every case.
  • Response: 201 with the new RunResponse (state: "open")
  • Errors: 422 when title is empty or statusFilter contains an unknown status
  • Audit: run.cloned with metadata.caseCount, metadata.sourceRun, metadata.cloneAssignees, metadata.statusFilter
  • Works on any closed source run — completed or aborted. When cloneAssignees is true, each carried-over case keeps the source case’s assignee; otherwise every case in the new run starts unassigned.
  • Role: admin or owner only (member / viewer403)
  • Response: 204 No Content
  • Audit: run.deleted with metadata.runAggregates, wasState, and closedAt captured before the cascade. After delete, run_id on audit rows (including this one) is set to null by FK cascade; the event remains visible in org-wide listings.

Audit listing endpoints are read-only. Use the run routes above (and other product APIs) to produce new audit rows.

These actions are emitted for threaded comments on entity pages, including replies (parentUlid set) and pin/unpin. The commented, comment_updated, and comment_deleted actions include metadata.commentUlid and metadata.excerpt (first 240 characters of the comment body); comment_pinned and comment_unpinned include only metadata.commentUlid (pinning carries no body excerpt).

ActionEmitted whenEntity type
defect.commentedA comment or reply is added to a defectdefect
defect.comment_updatedA comment on a defect is editeddefect
defect.comment_deletedA comment on a defect is deleted (once per cascaded reply too)defect
defect.comment_pinnedA top-level comment on a defect is pinneddefect
defect.comment_unpinnedA top-level comment on a defect is unpinneddefect
test_case.commentedA comment or reply is added to a test casetest_case
test_case.comment_updatedA comment on a test case is editedtest_case
test_case.comment_deletedA comment on a test case is deleted (once per cascaded reply too)test_case
test_case.comment_pinnedA top-level comment on a test case is pinnedtest_case
test_case.comment_unpinnedA top-level comment on a test case is unpinnedtest_case

Example:

{
"action": "test_case.commented",
"entityType": "test_case",
"entityLabel": "TC-1 Login with valid credentials",
"metadata": {
"commentUlid": "01J…",
"excerpt": "Reproduced on staging with fresh session."
}
}

Create and revoke API tokens under API authentication (/workspace/api-tokens in the product UI).