Skip to content

This tool is not affiliated with, endorsed by or sponsored by Okta, Inc. Okta is a trademark of Okta, Inc. Other names are trademarks of their respective owners.

How to Export the Okta System Log (API, CSV, SIEM)

Export the Okta System Log for an investigation: Admin Console CSV, /api/v1/logs with correct pagination, Log Streaming, SIEM exports and the traps to avoid.

Published on 6 min read

TL;DR. For an investigation, pull the whole org for the whole 90-day retention from GET /api/v1/logs with a bounded since/until window, limit=1000, and follow the Link: rel="next" header until it disappears. Never page by moving since yourself. Use the Admin Console CSV only for a quick look, and SIEM or Log Streaming data when you need more than 90 days. Keep the raw JSON: every other format loses fields.

The export is where most Okta investigations quietly go wrong. Not because it is hard, but because the obvious shortcut (search for the suspect user, click download) produces a file that looks complete and is not. This article is the long version of the checklist on the tool home page.

What you are exporting

The Okta System Log is a stream of LogEvent objects. Each has an eventType, an actor, a target array, client (IP, user agent, geolocation), securityContext (ASN, AS organisation, ISP, proxy flag), authenticationContext (including the session identifier), outcome, debugContext.debugData and a unique uuid. The schema is documented in the System Log API reference.

Two properties of the data drive every decision below:

  • Retention is 90 days. Okta states that data older than 90 days is not returned (System Log query guide). If the incident may be older, the only sources left are whatever was streamed or collected before.
  • Correlations need other people's events. A helpdesk reset is logged with the helpdesk agent as actor and your victim as target. A role grant is logged with the attacker as actor and the new admin as target. Filter on one user and you lose half the story.

This gives you the original JSON, every field, and a repeatable procedure.

Credentials

Use a read-only path: an API token created by a read-only administrator, or an OAuth 2.0 service app granted the okta.logs.read scope. An Okta API token carries the permissions of the admin who created it (Okta Help Center: API tokens), so do not create it from a Super Administrator for this job, and revoke it when the export is done. During an active incident, also make sure the account you use is not one the attacker may control.

Bounded requests and pagination

The query guide distinguishes two kinds of requests:

Bounded requestPolling request
Parameterssince and until setno until, sortOrder=ASCENDING
Useexport a fixed periodfollow new events continuously
Endlast page has no next linkalways returns a next link
Orderingby publishedmay be out of order

For forensics you want a bounded request. The guide is explicit on two points that matter: follow the next links rather than paging manually with since and until (manual paging can skip or duplicate events), and some events for a recent range may arrive late. Export up to "now minus a few minutes", and re-run the last hours later if the incident is live.

A minimal shell loop:

url="https://${OKTA_DOMAIN}/api/v1/logs?since=2026-06-15T00:00:00Z&until=2026-09-13T00:00:00Z&limit=1000"
n=0
while [ -n "$url" ]; do
  n=$((n+1))
  curl -sS -D headers.txt \
    -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
    -H "Accept: application/json" \
    "$url" > "okta-system-log-$(printf %04d $n).json"
  url=$(tr -d '\r' < headers.txt | grep -i '^link:.*rel="next"' \
        | sed -E 's/^[Ll]ink: <([^>]+)>.*/\1/')
  sleep 1   # stay well under the rate limit
done
gzip okta-system-log-*.json

Each page is a JSON array. You do not need to merge them: the analyzer accepts many files at once, and also arrays written back to back in one file. Events are de-duplicated by uuid, so overlapping pages or a re-run are harmless.

limit goes up to 1,000 events per page. Queries are subject to rate limits and a 30-second timeout per query, so a very busy org over 90 days means many pages; let it run.

Filtering on the server: only for triage

The API supports a filter expression such as eventType eq "user.mfa.factor.reset_all" and a keyword q parameter (query guide). They are useful to answer one quick question. For the evidence export, leave them out.

Option 2: Admin Console CSV

In the Admin Console, Reports › System Log, set the period, leave the search empty, and use Download CSV (Okta Help Center: System Log). It needs no token and is fast for a short period.

The trade-off is fidelity: a CSV flattens nested objects, and the exact column set of the console download is not something I could verify from public documentation. The analyzer reads CSV whose headers are LogEvent paths (actor.alternateId, client.ipAddress, target[0].displayName, securityContext.asNumber and so on) or a _raw column holding the original JSON. If a console CSV yields fewer findings than you expect, redo the export through the API before concluding anything.

Option 3: Log Streaming (EventBridge, Splunk Cloud)

Okta can stream System Log events in near real time to Amazon EventBridge or Splunk Cloud; a super admin configures it under Reports › Log Streaming (Okta Help Center: log streaming). A stream only forwards events going forward, so it is a retention strategy, not a recovery tool.

If a stream already exists, export the period from its destination:

  • EventBridge archives (S3, CloudWatch Logs) keep each LogEvent in the detail field of the EventBridge envelope. JSON or JSON Lines, gzipped or not, can be dropped as-is.
  • Splunk: search the Okta source type and export results as JSON or CSV. The _raw field carries the original event and is used when present; result envelopes are unwrapped.

Option 4: other SIEMs

The rule is the same everywhere: export the raw events with the original field names.

  • Elastic: documents are unwrapped from _source automatically.
  • Generic JSON Lines with one LogEvent per line works directly, as do syslog-prefixed lines that end with the JSON event.
  • Microsoft Sentinel: the classic Okta_CL custom table flattens fields into suffixed columns such as actor_alternateId_s; the analyzer does not map those yet. If the original JSON is available, export that instead.

A pre-flight checklist

Before you hand the export to anyone, or to the analyzer:

  • The period covers at least several days before the first suspicious event (baselines need history).
  • No filter or search was applied.
  • The first and last published timestamps match the window you asked for.
  • The files are kept unmodified, with hashes, somewhere the attacker cannot reach.
  • The token or service app used for the export is revoked or disabled.

The next step is analyzing the export. If you are still deciding what to look for, start with the Okta compromise investigation guide.

Further reading

Related articles

A fictional Okta intrusion read event by event: helpdesk reset, attacker factor, Super Admin grant, rogue IdP and AWS access, with the analyzer's findings.
The Okta System Log eventTypes that matter in an incident, grouped by attack stage, with the fields to read and the Classic vs Identity Engine differences.
Analyze an Okta System Log export in your browser: load the files, read the verdict and findings, pivot on users, IPs and sessions, and export the timeline.

This tool is not affiliated with, endorsed by or sponsored by Okta, Inc. Okta is a trademark of Okta, Inc. Other names are trademarks of their respective owners.