Skip to content

Audit log streaming

A log stream forwards audit events to a customer’s own logging destination as they happen. Each stream is a destination plus a cursor into the audit trail, driven by a dedicated pump that batches, delivers, and advances the cursor.

This is how audit data outlives the platform’s own 365-day retention, and how a security team gets platform events in the same place as the rest of their telemetry.

Requires the logstreams:write permission.

POST /v1/organizations/org_01J…/log_streams
Authorization: Bearer sk_live_…
{
"type": "datadog",
"token": "<datadog api key>"
}
{
"stream_id": "ls_…",
"type": "datadog",
"endpoint": "https://http-intake.logs.datadoghq.com/api/v2/logs",
"status": "active",
"cursor_id": 84213,
"last_delivery_at": null,
"last_error": null,
"created_at": 1750000000000
}

The token is stored encrypted and never returned again — subsequent GETs show everything above except the token.

typeendpointtoken
datadogOptional. Defaults to the US intake; set it for other sites.Required — a Datadog API key.
splunk_hecRequired — your HEC collector URL.Required — the HEC token.
httpsRequired — any HTTPS collector.Optional — sent as Authorization: Bearer.

Endpoints are checked against the egress policy before the stream is created, so a destination that resolves somewhere it should not is refused up front rather than at first delivery.

A new stream’s cursor is set to the current high-water mark. It forwards events from creation onward and does not backfill history. To load the past, page GET /v1/events from after=0 once, then let the stream take over.

Every destination receives the same event shape, wrapped for its native intake.

The common event:

{
"id": 84214,
"timestamp": "2026-08-16T12:00:00.000Z",
"event": "login_success",
"actor": "usr_01J…",
"organization_id": "org_01J…",
"data": { "method": "pwd+totp" }
}

Datadog — a JSON array, DD-API-KEY header, each entry tagged by org:

[
{
"ddsource": "oauth.work",
"ddtags": "org:org_01J…",
"service": "audit",
"message": "{\"id\":84214,…}"
}
]

Splunk HEC — newline-concatenated envelopes, Authorization: Splunk <token>:

{"time":1750000000.0,"sourcetype":"oauth-work:audit","event":{"id":84214,}}
{"time":1750000001.0,"sourcetype":"oauth-work:audit","event":{"id":84215,}}

Generic HTTPS — a single object with the batch under events:

{ "events": [ { "id": 84214, }, { "id": 84215, } ] }

Your collector should return 2xx. Any other status is treated as a failed batch.

Batches are up to 100 events. When a batch comes back full the pump returns in about a second, assuming there is a backlog to drain; when the stream is caught up it idles at roughly 30 seconds.

Delivery is at-least-once. The cursor advances only after the destination accepts the batch, so a batch delivered whose cursor write is then lost will be re-shipped. Deduplicate on the event id, which is stable and monotonic.

On failure the pump backs off — 1 minute, then 5, then 30, holding at 30 — and records the reason on the stream:

GET /v1/organizations/org_01J…/log_streams
{
"data": [
{
"stream_id": "ls_…",
"status": "active",
"cursor_id": 84213,
"last_delivery_at": 1750000000000,
"last_error": "destination returned 503"
}
]
}

last_error and a cursor_id that stops moving are the two things to watch. Because the database is the source of truth, a deleted stream simply stops at its next wake — there is nothing to cancel.

DELETE /v1/organizations/org_01J…/log_streams/ls_…

Events continue to accumulate in the audit log; only forwarding stops. Creating a new stream later starts from that later moment, not from where the deleted one left off.

An IT admin can set up a stream themselves, without an API key, through an Admin Portal link minted with intent: "log_streams". That is usually the right answer for enterprise customers: the destination and its token belong to them, and this way the token never passes through your support channel.