Audit Log

The audit log is a record of the significant things done in your account, by your team and by your end users: who did it, to what, when, and from which address. Nothing can edit it and nothing can delete an entry early — not you, not an API key, not us through the product. Entries are removed only when they pass the retention window of your plan.

You can read it in the dashboard, under Audit log, or through the API (below). Both show the same entries.

There is one log per account. Entries that belong to a project are tagged with it; entries that belong to the whole account (your social login providers, your security settings, your webhook endpoints, the log's own retention) carry no project.

In the dashboard

Open Audit log in the dashboard's menu. The tab shows the log of the project you have selected — its own entries and the account-wide ones — newest first, with the time in your locale (hover for UTC), a readable label for each action, who did it, to what, from which address and device, and a Details control that opens the few facts the entry holds. You can filter by period (the last 24 hours, 7 or 30 days, or a range you choose), by actor, by action and by type of actor, and load more entries as you go. A team member is shown by the email address of their account, matched from the project's member list; an end user or an API key is shown by a short form of its ID.

  • Who sees it: the project's owners and admins. A plain member of the project sees a message saying so.
  • Retention: the tab shows the current window and your plan's maximum. The account owner can shorten it there, or return to the plan's window. Shortening asks for confirmation first, because entries older than the new window are deleted at the next hourly sweep and cannot be recovered.

What is recorded

ActionDone byWhat happened
key.createdteam memberAn API key was created. The entry has the key's name and scopes, never the key.
key.revokedteam memberAn API key was revoked. Revoking a key that is already revoked is not an action and is not recorded again.
project.createdteam memberA project was created.
project.member_addedteam memberSomeone was added to a project, with their role.
project.member_removedteam memberSomeone was removed from a project, or left it.
project.ownership_transferredteam memberA project was handed to another member.
gate.oidc_providers_updatedteam member or API keySocial login providers were switched on or off.
gate.security_updatedteam member or API keyThe passwordless and MFA switches were changed.
gate.return_urls_updatedteam member or API keyA project's social-login return addresses were replaced.
gate.webhook_created, gate.webhook_updated, gate.webhook_deletedteam member or API keyA Gate webhook endpoint was registered, changed or removed. The entry says whether the address changed, never what it is.
passepartout.settings_updatedteam memberThe Telegram bot settings were changed. The entry says whether the token was replaced, never the token.
lifecycle.rule_set, lifecycle.rule_removedteam member or API keyAn automatic-deletion rule was switched on, changed or removed.
audit_log.retention_changedteam memberThe retention window of this log was changed.
organization.role_created, organization.role_updated, organization.role_deletedAPI keyA role of an organization was defined, changed or deleted. The entry has the number of permissions, not the list.
organization.member_added, organization.member_role_changed, organization.member_removedAPI key, or NautilusAn end user was given a role, had it changed, or lost it. When a role's deadline passes and your rule removes the membership, Nautilus is the actor (system).
organization.deletedAPI keyAn organization was deleted, with its roles and members.
identity.loginend userAn end user signed in.
identity.password_changedend userAn end user changed their password.

Creating or renaming an organization is not recorded: it grants no access, and a backend that creates one per customer would bury the entries that matter. Creating, changing and deleting cron jobs and delayed jobs, Relay endpoints and end-user identities is not recorded either. New actions may be added over time; an action that exists is never renamed.

What an entry says about a person

An entry names a person by ID, never by email address, name or anything else that identifies them outside Nautilus. What our staff do on your account from the admin panel (for example when you ask support to change something) is recorded the same way, with the actor type staff. An end user's ID is the id Gate gave them; a team member's is their Nautilus account ID; an API key is named by its key ID. To show a name through the API, look the ID up on your side — for a team member, the project's member list in the dashboard shows the address behind each ID; the dashboard's Audit log tab does that matching for you.

Each entry also records the IP address and the user agent of the request. The address is the one that reached us — the client of Nautilus, as our reverse proxy saw it; for a sign-in it is the address the identity service recorded on the session it created. Two things follow:

  • When your backend drives a sign-in or a password change for its user through /v1/gate/auth/*, the address is your backend's, not your user's. When a person signs in in their own browser against Nautilus directly, it is theirs.
  • For a password change the address is the one the identity service recorded on the person's session, that is, where that session was opened from; it is not read from the request that changed the password. The user agent is that of the request the identity service received: when your backend calls /v1/gate/auth/settings/submit, your backend's own.
  • An address that is not a valid IP, or a user agent past 256 characters, is dropped or cut; it is never stored as sent.

When an entry is written

Actions in the dashboard and through the API are written in the same database transaction as the action. Either both happen or neither does: an API key cannot be created without its entry, a refused or failed action leaves no entry, and if the entry cannot be written the action fails with 500 and does not happen.

Two exceptions, stated plainly:

  • Gate webhook endpoints are held by our delivery service, which a database transaction cannot include. Nautilus changes the endpoint first and writes the entry right after, so there is never an entry for a change that did not happen. In the rare case that the change was made and the entry could not be written, the call answers 500 although the change stands.
  • A sign-in and a password change happen inside Kratos, the identity service, not in our database. They are written when Kratos tells us, each from its own Kratos hook: a sign-in when Kratos runs its after-login hook, a password change when Kratos runs its settings.after.password hook, that is, when the change has been saved. The entry is then written in the same transaction as the record of the webhook event we already keep for that event, so the log and your webhook always agree. Consequences:
    • An entry appears a moment after the sign-in or the change, not within it.
    • A sign-in Kratos does not report is not recorded. A failed sign-in is not reported by Kratos, so failed attempts are not in the log. A password change Kratos refuses is not a change and is not recorded either.
    • Every way of changing a password is recorded, once: through /v1/gate/auth/settings/submit, by posting to the ui.action of a settings flow, and in Kratos' own browser settings flow. Before 2026-10 only the first was; a change made in a flow that never went through /v1/gate/auth/settings/submit has no entry. Because the entry now comes from Kratos' report and not from our gateway's answer, the entry and the identity.password_changed webhook for a change made through that route appear at a slightly different moment than they used to: when Kratos reports the change, not when our gateway has Kratos' answer.
    • The project of a password change is the project of the key that started the settings flow. A flow no key started (Kratos' own browser flow) belongs to no project; its entry is shown, like the entries of account-level actions, under every project of the account.
    • The report is made once and not retried. If our gateway is down at that moment the change stands and has no entry and no event.
    • Only your end users' password changes are recorded; a dashboard account's password is not.
    • A sign-in through Telegram (Passepartout) is not recorded.

Retention

Entries are kept for the window of your plan and then deleted, hourly:

PlanWindow
Free7 days
Pro30 days
Advanced90 days
Contractas agreed in the contract; 90 days if it says nothing

The account owner can shorten the window, from one day up to the plan's window, and cannot lengthen it. If you move to a smaller plan, the plan's window applies from then on, whatever you chose before. An entry past the window is no longer returned, even before the hourly deletion has removed it, and entries removed this way do not come back. Shortening the window removes older entries at the next hourly deletion.

Read the log

GET https://api.vernesoft.com/v1/gate/audit-log
Authorization: Bearer vrn_...

Requires an API key with the gate scope. The key reads its own project's entries and the account-wide ones — never another project's, never another account's. The log is read-only: no route changes or deletes an entry.

Any key with the gate scope can read the whole log, including the IP addresses and user agents of your team members — the people who use your dashboard — and not only those of your end users. There is no separate scope for the log, so treat a gate key as having that access, and keep it on your backend. If you do not want a service to see your team's addresses, do not give it a gate key.

Query parameterMeaning
limitEntries per page, 1 to 100. Default 20.
cursorThe next_cursor of the previous page.
actionOne action from the table above, e.g. key.created.
actor_idOnly entries whose actor has this ID — an end user, a team member or an API key.
actor_typeteam_member, api_key, end_user, system or staff.
target_idOnly entries whose target has this ID — the end user whose role changed, the key that was revoked.
fromOnly entries at or after this instant (RFC 3339, e.g. 2026-10-13T09:30:00Z).
toOnly entries before this instant. Must be later than from.

Filters combine: ?action=identity.login&actor_id=...&from=... is the sign-ins of one person since a date.

curl 'https://api.vernesoft.com/v1/gate/audit-log?action=key.created&limit=2' \
  -H 'Authorization: Bearer vrn_...'

Entries come newest first. While has_more is true, pass next_cursor as cursor to get the next page; keep the same filters. The cursor is opaque: pass it back, do not parse it. Entries written while you page do not appear in the pages after the one you are on.

The entry

FieldTypeMeaning
idUUIDUnique per entry.
occurred_atRFC 3339, UTC, microsecondsWhen the entry was written.
actionstringOne of the actions above.
actor.typestringteam_member (your team in the dashboard), api_key, end_user, system (Nautilus, on a rule you set), staff (a member of Nautilus staff acting on your account from the admin panel, named by their own ID), or unknown for a request that arrived without attribution (not expected).
actor.idUUID or nullWho. null when it cannot be known.
targetobject or nullWhat the action was done to: { "type", "id" }. Types include api_key, project, identity, role, organization, webhook_endpoint, lifecycle_rule and setting.
project_idUUID or nullThe project, or null for an account-wide action.
detailsobjectA few facts about the action — scopes, a role change's old and new role, which fields a webhook update touched. Never an email address, a key, a token or a webhook address.
ipstring or nullThe request's IP address.
user_agentstring or nullThe request's user agent, up to 256 characters.

Fields are added over time, never renamed or removed; read the ones you need and ignore the rest.

Errors

Errors use the standard envelope:

{ "error": { "code": "invalid_payload", "message": "Query parameter `from` must be an RFC 3339 timestamp, e.g. 2026-10-13T09:30:00Z.", "request_id": "req_..." } }
StatusCodeWhen
400invalid_payloadA parameter is malformed: an unknown action, actor_id that is not a UUID, a bad timestamp, from not before to, a bad cursor.
401unauthorizedNo key, or an invalid or revoked one.
403forbiddenThe key does not have the gate scope.