Skip to main content
This guide shows you how to poll organization audit events with the Audit Events Export API. After you connect a poller or an incremental connector, you can land those records in a data warehouse, SIEM, or compliance platform. The feed combines Writer’s two audit contracts (legacy administration events and newer platform audit events) into one stream, discriminated by a source field on every row. You do not need separate pollers for each contract.

Prerequisites

Before you export audit events, make sure you have:

Call the export endpoint

Request audit events for one organization. URL: GET https://api.writer.com/api/observability/v1/organization/{organizationId}/audit/events/export Call it with order=tail and no cursor or start_date to start at the current time. Only newly settled rows stream forward.

Authenticate requests

Send the Writer API key on every request.
Each key is bound to a single organization and can export audit events only for that organization. A request that uses a key against a different organizationId returns 403 Forbidden. Missing or malformed credentials return 401 Unauthorized at the public authentication boundary.

Choose a start point

You choose where the stream begins. After the first response, page with the cursor alone. The cursor already encodes your position and, for a browse continuation, the window it was cut from.

Start from the current time

Call the endpoint with order=tail and neither cursor nor start_date. The stream starts at the current time, with no backfill.

Backfill from a date

Pass start_date with order=tail to request retained events from that ingestion time, then continue polling after the backlog drains. start_date accepts an ISO-8601 timestamp, epoch seconds, or epoch milliseconds. For example, start_date=2026-07-01T00:00:00Z.

Browse a historical window

Pass both start_date and end_date with order=browse (the default) to walk a bounded [start_date, end_date] window newest-first. The response’s total_count is the exact number of events in the window, independent of limit and cursor. Omit both dates to browse the default window (now minus 90 days through now).

Stay inside the retention window

A start_date older than the retention window (90 days by default) returns 400 Validation Error. Available history depends on completed backfills; this does not guarantee full historical coverage.

Page with the cursor alone

After you store x-next-cursor, send it as the cursor query parameter and omit start_date and end_date. Sending either date together with cursor returns 400 because the cursor already encodes the window. The cursor is scoped to the order (browse or tail) that issued it; presenting a browse cursor with order=tail (or the reverse) returns 400.

Set query parameters

These parameters control paging and the browse window. The audit export has no filters, no format option, and no include_bodies option. It always returns native JSON. For order=browse, provide both start_date and end_date, or omit both. Providing only one date returns 400 Validation Error.

Read the response

Responses put the resume token and the caught-up signal in headers. The body mirrors them in pagination so a consumer can read either.

Read response headers

Use these headers to resume and, in tail mode, decide when the settled backlog is drained.

Read the body

The body is a JSON object. result holds the audit event rows. On a browse response, total_count repeats x-total-count. pagination.next_cursor mirrors x-next-cursor while another page is available, and is null on the last browse page; a tail response always sets it. For example:

Read row fields

Every event row carries both ordering axes and the stored actor record. Use event_id as the upsert key.
The export row reflects the stored audit record. The actor’s current directory name, email, and avatar are not joined in on the export, so historical events stay stable when users rename or leave the organization. Use actor_id to join against your own directory snapshot when you need the current identity.

Store and resend the cursor

The cursor is a versioned, HMAC-signed token. Treat it as an opaque string. Store it verbatim and send it back unchanged on the cursor parameter.
Do not parse, truncate, or reconstruct the cursor. A tampered or version-incompatible token returns 400 Validation Error with detail Invalid cursor. A browse cursor also carries the (start_date, end_date) window it was cut from, so changing the window on a continuation is rejected with cursor was issued for a different from_ms/to_ms window; restart paging without a cursor.

Apply delivery guarantees

Tail mode is an ordered revision feed. The settlement delay does not make an event immutable, and a cursor identifies a position rather than a fixed snapshot.
  • Browse ordering. Browse pages return in (event_time_ms DESC, event_id DESC) order within the requested window. Browse collapses to the latest revision per event_id.
  • Tail ordering. Tail pages return in (inserted_at_ms ASC, event_id ASC) order. Replaying a cursor returns no row that ordered before that cursor.
  • Settled window. A tail page contains only rows whose inserted_at_ms is at or before x-tail-watermark. The watermark is now minus a 5-minute settle lag. An event with an older event_time_ms and a newer inserted_at_ms arrives when the watermark passes that inserted_at_ms. Settlement is not record finality.
  • Revision-aware upserts. Later revisions can deliver the same event_id again on tail. Upsert on event_id, retaining the newest inserted_at_ms, so an older replay cannot overwrite a newer revision. Never permanently discard an ID because you have already seen it.
  • Replay. Replaying a cursor can observe newer rows at or after that position. After the stream is caught up, polling again can return newly settled rows.

Stay within limits

Each organization has a fixed window of 120 requests per 60 seconds by default. A request over that limit returns 429 Rate Limited with a retry-after header. The value is the whole seconds until the window resets, with a minimum of one second. The limit is checked before authentication. Wait for retry-after, then retry. limit is clamped to 1000. A larger value is reduced to 1000 rather than rejected.

Handle errors

Service error bodies use the Problem+JSON shape, with code and detail. Gateway authentication errors can use a different envelope: an unauthenticated public request can return 401 with generic JSON rather than the service Problem+JSON shape. Handle the HTTP status even when the body does not contain a service error code.

Poll for new audit events

Poll with the returned x-next-cursor to pick up newly settled events and revisions:
  • Drain and pause: Drain settled pages until x-has-more is false, then pause before polling again.
  • Upsert revisions: Upsert by event_id, retaining the newest inserted_at_ms. A primary key alone does not prevent older retries from overwriting newer revisions.
  • Persist cursor: Save the cursor only after the complete page is successfully processed. Restart from that saved cursor. These examples keep the cursor in memory only.
  • Handle rate limits: On 429, wait for retry-after before retrying. The 30-second pause in these examples applies after the stream is caught up.
The Python and JavaScript examples emit each complete row revision as JSON to stdout. The cURL example prints each response body.

Connect Airbyte, Fivetran, or Singer

The cursor and caught-up signal are header strings, so an incremental connector can store them as a bookmark. Read x-next-cursor from the header, emit every revision, and configure the destination to upsert on event_id, retaining the newest inserted_at_ms. Save the bookmark only after the destination processes the page. The same mapping that the Logs Export uses applies here, with event_id as the record primary key instead of log_id. On 429, honor retry-after before retrying.

Review default limits

These operational defaults apply to the audit events export. To request different values, contact your Customer Success team.

Next steps

Pull related telemetry alongside your audit events.
  • Export logs: Poll captured LLM calls, agent-run records, and agent feedback into a warehouse or SIEM
  • Event logs: View, filter, and search request logs in Control Center