Concepts
Audit log retention and export
An audit log is only worth having if somebody can get the events out of it, and only defensible if nobody can quietly change them on the way. Retention and export are therefore the two questions a security review always reaches — usually after the easy ones, and usually with a number already in mind.
Who asks, and what they actually want
Three different people ask about audit logs and they want different things. A customer's security engineer wants a live stream into their own tooling so that your events appear alongside everything else they monitor. A compliance officer wants to know how long events are kept and whether anybody can delete them. An auditor, once a year, wants a file covering a date range.
Building for only one of the three produces an integration that fails the other two. A streaming setup with no export leaves the auditor asking your support team for a CSV; an export with no stream means the security engineer finds out about a suspicious sign-in when they next go looking.
The good news is that all three read the same events. What differs is the delivery: continuous, on demand, and long-term.
Deciding a retention period
Retention is a policy question with a technical consequence, and the technical consequence is cost — audit events are small individually and substantial in aggregate for a busy tenant. The periods below are the ones customers typically ask for; which applies is decided by their regulator and their contract, not by you.
| Period | Who asks for it | Notes |
|---|---|---|
| 30 to 90 days | Operational monitoring | Enough for incident investigation. Not enough for an audit. |
| 1 year | Most commercial contracts | The common default when nothing more specific is required. |
| 7 years | Regulated industries | Financial and healthcare customers frequently ask by name. |
| Indefinite | Rare, and usually a misunderstanding | Worth asking what the underlying requirement is before agreeing. |
Longer retention interacts with data protection obligations rather than overriding them. An audit log recording that a person performed an action is usually retainable on legitimate-interest grounds; the same log padded with unnecessary personal detail is harder to defend. Record what happened, not everything you knew at the time.
Append-only, and what that means
Events cannot be edited or deleted through the API. There is no update endpoint and no delete endpoint, and that absence is the feature: a log that can be corrected is a log whose contents are an assertion rather than a record.
The practical consequence for you is that a mistake in what you emit is permanent. An action name that turns out to be wrong stays wrong for events already written, which is the argument for registering schemas and versioning them rather than iterating on the shape in production. Emit at the point the change commits, and emit the truth rather than the intention.
Exports are asynchronous for a reason
The useful export ranges are large: a quarter of events for a busy tenant is not something to assemble inside an HTTP request. So an export is started, polled, and collected — three calls rather than one, in exchange for a range that can actually be requested.
- 1Start the export with an organization, a range, and optionally a list of actions to include.
- 2Poll with a backoff. A tight loop achieves nothing except rate limiting; double the interval up to a sensible ceiling.
- 3Collect the signed URL once the state is ready. It is time-limited by design.
- 4Hand the URL to the customer's browser rather than storing it. Storing a signed URL turns a short-lived grant into a long-lived one.
- 5Emit your own audit event recording that an export was taken, by whom and for what range. An export is a bulk read of sensitive data and belongs in the log like any other.
1const job = await paycux.auditLogs.createExport({2 organizationId: "org_01HQ8ZK3M4N5P6R7S8T9V0W1X2",3 rangeStart: new Date("2026-04-01"),4 rangeEnd: new Date("2026-06-30"),5 format: "csv",6});78// Geri cekilerek bekle — sabit araliksiz.9let wait = 1000;10let state = job;1112while (state.state === "pending") {13 await new Promise((r) => setTimeout(r, wait));14 wait = Math.min(wait * 2, 30000);15 state = await paycux.auditLogs.getExport(job.id);16}1718await auditLog.emit("audit_log.exported", { rows: state.rowCount });19return state.url;
Letting customers take their own
The version that scales is the one where you are not involved. A customer who can start an export from their own settings page does not open a ticket, and their security team does not wait on your working hours during an incident.
That means exposing the export flow in your product, scoped to the requesting tenant and gated on a permission of your own — the customer's admins, not everybody. Combined with a continuous stream for the security team, it covers all three of the audiences this guide started with, and leaves your support queue for questions that need a human.