Skip to main content

Audit log

Every significant action in Orunbase — a role change, a config write, a billing event, a GitHub push — is recorded as a structured event and projected into an immutable audit log you can query per workspace. The write path is append-only: events and their audit entries are inserted in the same transaction as the change they describe, and no API exists to update or delete them.

The event envelope

Events share one envelope with four identity blocks — actor (who), tenant (where), subject (what), and trace (how it correlates):

{
"id": "0d3f7a1c-8b2e-4f5a-9c6d-1e2f3a4b5c6d",
"type": "membership.updated",
"version": 1,
"source": "membership-worker",
"occurredAt": "2026-07-01T14:22:31.004Z",
"actor": {
"type": "user",
"id": "usr_2b3c4d5e6f7a",
"sessionId": "ses_8f9e0d1c2b3a",
"ip": "203.0.113.42"
},
"tenant": {
"orgId": "org_1f2e3d4c5b6a",
"projectId": null,
"environmentId": null
},
"subject": {
"kind": "member",
"id": "mem_7c8d9e0f1a2b",
"name": null
},
"trace": {
"requestId": "req_a1b2c3d4e5f6",
"correlationId": null,
"causationId": null,
"idempotencyKey": "promote-priya-1"
},
"payload": { "memberId": "mem_7c8d9e0f1a2b", "previousRoles": ["viewer"], "role": "builder" },
"audit": { "redact": [] }
}
  • actor.type is one of user, service_principal, workflow, or system — automated actions (stale-environment sweeps, webhook drains) are attributed to system, CI runs to workflow.
  • tenant scopes the event to a workspace and, where applicable, a project and environment.
  • trace.requestId links the event back to the API request that caused it (the same requestId you see in every response's meta).
  • audit.redact lists payload paths to mask before the entry is served — redacted fields appear as "[REDACTED]" in query results.

The audit projection

Each event that matters for compliance is projected into an audit entry: the envelope fields flattened for querying, plus a human-readable category (membership, config, billing, …) and description ("Created project "Checkout Service""). Entries are immutable — the projection is written once, atomically with the event, and served read-only with redaction applied.

Query the audit log

GET /v1/organizations/{orgId}/audit

Requires the audit.read permission (held by workspace owner and admin). All filters combine with AND semantics and never change the ordering — entries come back newest-first with cursor pagination.

Query parameterDescription
categoryAudit category, e.g. membership, config, billing, integrations
actorIdThe actor id recorded on the event
actorTypeOne of user, service_principal, workflow, system
subjectKindSubject/resource kind, e.g. project, member
subjectIdSubject/resource id
eventTypeExact event type, e.g. membership.updated
fromInclusive lower bound on occurredAt (ISO-8601 with milliseconds, Z)
toInclusive upper bound on occurredAt (same format)
limitPage size, 1–100 (default 50)
cursorOpaque continuation cursor from meta.cursor
curl "https://api.orunbase.com/v1/organizations/org_1f2e3d4c5b6a/audit?category=config&from=2026-06-01T00:00:00.000Z&limit=50" \
-H "Authorization: Bearer $ORUN_CLOUD_TOKEN"
{
"data": {
"auditEntries": [
{
"id": "6b1c9d2e-3f4a-5b6c-7d8e-9f0a1b2c3d4e",
"eventId": "0d3f7a1c-8b2e-4f5a-9c6d-1e2f3a4b5c6d",
"orgId": "org_1f2e3d4c5b6a",
"projectId": "prj_5e6f7a8b9c0d",
"environmentId": null,
"actorType": "user",
"actorId": "usr_2b3c4d5e6f7a",
"eventType": "secrets.updated",
"source": "config-worker",
"category": "config",
"description": "Secret metadata revoked: stripe.webhook_signing",
"subject": { "kind": "secret", "id": "8a7b6c5d-4e3f-2a1b-0c9d-8e7f6a5b4c3d", "name": null },
"occurredAt": "2026-06-20T10:03:11.482Z",
"requestId": "req_a1b2c3d4e5f6",
"correlationId": null,
"payload": { "operation": "revoke", "scope": "project", "key": "stripe.webhook_signing" }
}
]
},
"meta": { "requestId": "req_f6e5d4c3b2a1", "cursor": "eyJ0IjoiMjAyNi0wNi0yMFQxMDowMzoxMS40ODJaIiwiaSI6ImF1ZF8uLi4ifQ==" }
}

Iterate with the cursor

Pass meta.cursor back as ?cursor= until it comes back null:

CURSOR=""
while : ; do
RESP=$(curl -s "https://api.orunbase.com/v1/organizations/org_1f2e3d4c5b6a/audit?limit=100${CURSOR:+&cursor=$CURSOR}" \
-H "Authorization: Bearer $ORUN_CLOUD_TOKEN")
echo "$RESP" | jq -r '.data.auditEntries[] | [.occurredAt, .eventType, .description] | @tsv'
CURSOR=$(echo "$RESP" | jq -r '.meta.cursor // empty')
[ -z "$CURSOR" ] && break
done

The SDK ships a purpose-built async iterator that walks every page for you (with loop guards against cursor cycles), plus a single-page variant and an NDJSON export:

import { OrunCloud } from "@saas/sdk";

const client = new OrunCloud({
baseUrl: "https://api.orunbase.com",
auth: { kind: "bearer", token: process.env.ORUN_CLOUD_TOKEN! },
});

// Walk every matching entry across all pages
for await (const entry of client.events.iterAuditEntries("org_1f2e3d4c5b6a", {
by: "org",
category: "membership",
from: "2026-06-01T00:00:00.000Z",
})) {
console.log(entry.occurredAt, entry.eventType, entry.description);
}

// Or fetch one page and manage the cursor yourself (paginated UIs)
const { entries, cursor } = await client.events.listAuditEntriesPage("org_1f2e3d4c5b6a", {
by: "org",
limit: 50,
});

// Or stream the whole filtered log as NDJSON
for await (const line of client.events.exportAuditEntriesNdjson("org_1f2e3d4c5b6a", { by: "org" })) {
process.stdout.write(line);
}

The query object also supports by: "target" with subjectKind + subjectId to pull the history of one specific resource.

tip

The audit family has a higher rate-limit budget than most read surfaces — 120 requests per identity and 600 per workspace per 60 s — sized for export loops. See Rate limits.

What lands in audit

Every worker that mutates state appends to the same log. Representative categories:

CategoryExamples
membership, invitationMembers added/removed, role changes, invitations created/revoked
projectsproject.created, environment.created, environment.archived (including system-actor stale-archival)
configSetting/flag writes, secret create/rotate/revoke (values never appear — write-only by construction)
billingPlan assignments and billing lifecycle events
webhooksEndpoint changes and webhook.delivery.* lifecycle
integrationsGitHub App connect/setup, repo links, and normalized scm.* events (scm.push, `scm.pull_request.opened
api_keys, securityAPI key issuance/revocation, security-relevant events
supportAdmin-plane support actions (support.action_recorded, support.access_denied) — staff access to your workspace is itself audited
runs, objects, catalog, workspace_linksState-plane activity

Immutability and retention

  • Append-only writes. The storage layer exposes only inserts for events and audit entries; there is no update or delete path, in the API or internally.
  • Atomic with the change. Audit entries are written in the same transaction as the mutation they record — a change cannot commit without its audit trail.
  • Redaction at read time. Sensitive payload paths declared on the event are masked as "[REDACTED]" when entries are served; the raw values are never returned through the audit API.
  • System actions included. Automated behavior (stale-environment sweeps, integration drains) is recorded with actorType: "system", so the log is complete, not just user-initiated.

Entries are retained indefinitely at the storage layer today — there is no automatic purge. Use the NDJSON export to mirror the log into your own retention pipeline.