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.
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.
Option 1: the System Log API (recommended)
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 request | Polling request | |
|---|---|---|
| Parameters | since and until set | no until, sortOrder=ASCENDING |
| Use | export a fixed period | follow new events continuously |
| End | last page has no next link | always returns a next link |
| Ordering | by published | may 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
detailfield 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
_rawfield carries the original event and is used when present;resultenvelopes 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
_sourceautomatically. - 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_CLcustom table flattens fields into suffixed columns such asactor_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
publishedtimestamps 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
- Okta Developer: System Log query guide (bounded vs polling requests, filters, retention)
- Okta Developer: System Log API reference
- Okta Help Center: log streaming
- Okta Help Center: API token management