Documentation menu
SDK

Export the sign-in log

Download your app’s sign-in log as a CSV from the portal, or pull it into your own SIEM or data store with the SDK — in all six languages.

What the log holds

One row per sign-in event for an app: push approvals, one-time codes, machine (SSH and desktop) sign-ins, and gaps where a machine could not report. The CSV header and the SDK keys use the same fields, in this order.

FieldMeaning
idUnique id of the row.
occurred_atWhen the sign-in happened, RFC 3339 UTC. For an offline machine this is the machine’s own time.
received_atWhen we recorded the row, RFC 3339 UTC.
user_identifierYour identifier for the user, or the machine’s local login when there is none.
local_loginThe local account name on the machine, when one applies.
kindpush, totp, machine or gap. See below.
originpush (a push approval), machine_report (a registered machine reported it) or gap (a gap marker). Empty on one-time-code rows and on rows recorded before machine reporting existed.
methodMachine rows only, empty otherwise: totp, backup_code, unknown (a code was tried but its type could not be told) or none (nothing was proved, because the machine let the sign-in through without a factor or had none to ask for).
outcomePush rows: approved, denied or expired. One-time-code rows from the SDK: verified. Machine rows: allowed or denied. Gap rows: gap.
reasonMachine rows: wrong_code, replayed_code, throttled, store_error or no_offline_factor on a denial; failopen, bypass_user, unenrolled_bypass or disabled on an allow with no factor. Gap rows: dropped (the machine discarded events) or missing (events never arrived). Empty otherwise.
city, region, countryCoarse location. Raw coordinates are never exposed.
new_countryTrue when the country is new for that user.
source_ipThe address the sign-in came from, when known.
machine_id, machine_nameThe machine, for machine rows.
serviceThe service signed in to, such as sshd or sudo.
contextThe context text shown to the user.
approval_idLinks the rows that describe the same push approval.
seqMachine rows only: the machine’s own sign-in counter, used to detect gaps. Empty on push and one-time-code rows.
gap_countOn a gap row, how many sign-ins are missing.
clock_skew_secondsHow far the machine clock was from ours, when reported.

The four kinds

  • push — a tap-to-approve request.
  • totp — a one-time code check.
  • machine — a sign-in on a registered Linux or Windows machine, reported by the machine.
  • gap — a marker that a machine was unable to report some sign-ins. gap_count says how many.

Download a CSV from the portal

Open an app and choose its Activity tab, set the filters you want and press Download CSV. The file holds exactly what those filters select, newest first, and matches the log on screen — an expired push is hidden when its machine report exists. The file is UTF-8 with a byte-order mark so Excel opens accented names correctly. Cells that start with =, +, - or @ are prefixed so a spreadsheet does not run them as formulas.

Pull it with the SDK

Load your saved cursor, request a page, store the rows, save the new cursor, and repeat while has_more is true. Run it on a schedule. Pages hold up to 1000 rows; leave the limit out to take the default of 500.

main.go
cursor := loadCursor() // "" on the first run
for {
	page, err := client.ListSignInLog(1000, cursor)
	if err != nil {
		return err
	}
	for _, ev := range page.Items {
		store(ev)
	}
	cursor = page.NextCursor
	saveCursor(cursor)
	if !page.HasMore {
		break
	}
}

Always save next_cursor, even when the page is empty: it is the position to resume from. A cursor that is not one we issued is refused with INVALID_CURSOR.

Oldest first, about 30 seconds behind

The feed is ordered oldest first, by the time we received each row. Rows are served about 30 seconds after they are recorded, so a fresh sign-in may not appear on the next pull. The portal CSV is newest first and has no such lag.

One expired push can be two rows

When a push expires and the machine later reports the same sign-in, the feed carries both rows, linked by the same approval_id. The portal screen and CSV show only the machine report; the SDK feed keeps both so you can reconcile them yourself.

Machine keys are refused

A key created for one registered machine cannot read the log: the call answers 403 MACHINE_KEY_NOT_ALLOWED. Create an ordinary app key in the portal and use that.

How far back it goes

Sign-ins are kept 7 days on Developer, 30 on Growth, 90 on Team and 365 on Scale. Pull on a schedule that is shorter than your plan’s window, and keep what you pull.