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.
Create a stream
Section titled “Create a stream”Requires the logstreams:write permission.
POST /v1/organizations/org_01J…/log_streamsAuthorization: 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.
Destination types
Section titled “Destination types”type | endpoint | token |
|---|---|---|
datadog | Optional. Defaults to the US intake; set it for other sites. | Required — a Datadog API key. |
splunk_hec | Required — your HEC collector URL. | Required — the HEC token. |
https | Required — 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.
Streams start at now
Section titled “Streams start at now”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.
Wire formats
Section titled “Wire formats”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.
Delivery behaviour
Section titled “Delivery behaviour”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 a stream
Section titled “Delete a stream”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.
Letting customers configure their own
Section titled “Letting customers configure their own”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.