- Organisation
- Paramètres
- Lire le journal d'audit avec l'API
Read the audit log with the API
The auditActions GraphQL query returns the audit events of your account and organisation: logins, MFA checks, password changes, exports and file downloads, shared link openings, setting changes and access changes. Each event carries its kind, its time, the user who made it and the client IP.
The query has two modes:
- Page mode lists events newest first, page by page, with sorting and filters. Use it to search the log or show it in a report.
- Cursor mode lists events oldest first, after the last one you read. Use it to poll the log from a SIEM or a security posture tool. See Poll for new events.
Who can read which events
| Caller | Events returned |
|---|---|
| Organisation admin, signed in | The organisation's events, and your own events. |
| Any other member, signed in | Your own account events only, such as your logins and password changes. |
| API key with the Admin or Security Auditor role | The organisation's events. |
| API key with any other role | None: the query returns an authorization error. |
The organisation's events need the action audit feature: it is part of some plans, and Ostorlab can also turn it on for an organisation. Without it, an admin sees only their own account events, and an API key receives an empty list. The hasActionAudits setting of the organisationSecurity query tells you whether the feature is on.
Call the query
Signed in, use the sandbox at https://api.ostorlab.co/apis/graphql, see Sandbox. With an API key, send the key in the X-Api-Key header to https://api.ostorlab.co/apis/graphql_token/:
curl -s https://api.ostorlab.co/apis/graphql_token/ \
-H "X-Api-Key: $OSTORLAB_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ auditActions(page: 1, numberElements: 20) { audits { id createdTime event action ipAddress } } }"}'
To give a tool read access to the audit log and nothing else, create a key with the Security Auditor role. See Connect a security posture tool.
Event fields
{
auditActions(page: 1, numberElements: 1) {
audits {
id
createdTime
event
action
ipAddress
organisation {
id
name
}
organisationUser {
id
email
username
firstName
lastName
role
}
}
}
}
{
"data": {
"auditActions": {
"audits": [
{
"id": "1841",
"createdTime": "2026-10-29T16:05:00+00:00",
"event": "alice created an API key.",
"action": "ACCESS_CHANGE",
"ipAddress": "203.0.113.7",
"organisation": { "id": 25, "name": "Example Corp" },
"organisationUser": {
"id": 12,
"email": "alice@example.com",
"username": "alice",
"firstName": "Alice",
"lastName": "Martin",
"role": "admin"
}
}
]
}
}
}
idincreases with every new event. Cursor mode orders and pages by it.eventis a sentence that describes the action, in English.actionis the kind of the event. See Event kinds.ipAddressis the client IP of the action. It isnullwhen the IP is unknown, for actions made through MCP, and for events recorded before IPs were stored.organisationisnullfor account events that do not belong to an organisation.organisationUserisnullfor actions made with an API key.roleis the user's role in the event's organisation.
Event kinds
action |
Recorded when |
|---|---|
LOGIN |
A user signs in. |
LOGOUT |
A user signs out. |
LOGIN_FAILED |
A sign-in fails. |
LOGIN_LOCKED_OUT |
An account is locked after too many failed sign-ins. |
PASSWORD_LOGIN_REFUSED |
A password sign-in is refused because the user must sign in with SSO. |
MFA_PASSED |
A user passes the MFA check. |
MFA_FAILED |
A user fails the MFA check. |
PASSWORD_CHANGED |
A user changes their password. |
PASSWORD_RESET |
A user resets their password. |
DATA_EXPORT |
A user or key exports data or downloads a file. |
SHARED_LINK_OPENED |
Someone opens a shared access link. |
SETTINGS_CHANGE |
An organisation security setting changes. |
ACCESS_CHANGE |
A member, invitation, API key, shared link or MFA device is added, changed or removed. |
OTHER |
Any other event, and events recorded before kinds were stored. |
LOGIN_FAILED,LOGIN_LOCKED_OUT,PASSWORD_LOGIN_REFUSEDandMFA_FAILEDare recorded at most once a minute for the same user and IP, so repeated attempts do not flood the log.- An action a user makes on their account, such as a login, an MFA check or a password change, is recorded once in the user's own log and once in each organisation they belong to that has the action audit feature. An organisation receives these events only while the feature is on.
Filters
These arguments work in both modes and can be combined. An event must match all of them.
| Argument | Returns the events |
|---|---|
actions: [AuditActionType!] |
Of these kinds, for example [LOGIN_FAILED, LOGIN_LOCKED_OUT]. All kinds when omitted. |
createdAfter: Date, createdBefore: Date |
Created on or after, or on or before, this date, for example "2026-10-01". Both dates are included. |
user: String |
Of the users whose username, email, first name or last name contains this text. |
organisationName: String |
Of the organisations whose name contains this text. |
searches: [String] |
Whose sentence, organisation name, or user's username, email or name contains at least one of these texts. |
actionId: Int |
With this id. |
byMe: Boolean |
Made by you. Each of your logins, logouts, MFA checks and password changes is listed once, instead of once per organisation. Not available with an API key. |
Text filters ignore case.
Page mode
Pass page to get one page of events and a pageInfo block.
{
auditActions(
page: 1
numberElements: 50
actions: [LOGIN_FAILED, LOGIN_LOCKED_OUT]
createdAfter: "2026-10-01"
orderBy: CreatedTime
sort: Desc
) {
audits {
id
createdTime
event
ipAddress
}
pageInfo {
count
numPages
hasNext
hasPrevious
}
}
}
numberElementsis the page size, 15 by default.orderByis one ofActionId,CreatedTime,OrganisationUserandOrganisation, andsortisAscorDesc. WithoutorderBy, events come newest first andsorthas no effect.- Leave out
pageto get every matching event in one response.pageInfois thennull. On a large log, use pages or cursor mode instead.
Cursor mode
Pass afterId or first to read the events after a cursor, oldest first.
query AuditActions($afterId: Int, $first: Int, $actions: [AuditActionType!]) {
auditActions(afterId: $afterId, first: $first, actions: $actions) {
audits {
id
createdTime
event
action
ipAddress
}
hasMore
nextAfterId
}
}
With the variables { "afterId": 1840, "first": 100 } the response looks like this.
{
"data": {
"auditActions": {
"audits": [
{
"id": "1841",
"createdTime": "2026-10-29T16:05:00+00:00",
"event": "alice created an API key.",
"action": "ACCESS_CHANGE",
"ipAddress": "203.0.113.7"
}
],
"hasMore": false,
"nextAfterId": 1841
}
}
}
afterIdreturns only the events with a greaterid. Omit it to start from the oldest event.firstis the page size. It is 100 by default and at most 1000. A larger value returns 1000 events.hasMoreistruewhen more events follow the last one returned.nextAfterIdis theidof the last returned event. When no event is returned, it is theafterIdyou sent, so the cursor never moves back. It isnullonly when you sent noafterIdand no event is returned.- An event is returned only once it is 30 seconds old, so a very recent event appears in a later call. This keeps a poller from skipping an event that was still being saved.
Cursor mode cannot be combined with page, orderBy or sort: the query returns an error if you do. It ignores numberElements, and first sets the page size. The filters still apply.
hasMore and nextAfterId are null in page mode.
Poll for new events
Keep the nextAfterId of the last response and send it as afterId in the next call. You then receive only events you have not read.
- First call. Query
auditActionswithfirstand withoutafterId. - Store the cursor. Save
nextAfterIdfrom the response. - Next call. Pass the saved value as
afterId. Events come back in increasingidorder. - Page through a backlog. While
hasMoreistrue, repeat the call with the newnextAfterId. - Idle poll. When no event is returned,
nextAfterIdis theafterIdyou sent: keep your saved cursor and poll again later.
If you filter by actions or another argument, keep the same filters on every call of the same cursor.
Errors
| Message | Cause |
|---|---|
Authorization error: You do not have the required permissions to perform this action. |
The API key has a role other than Admin or Security Auditor. |
afterId and first cannot be combined with page, orderBy or sort. |
Cursor mode and page mode arguments were sent together. |
byMe filter is not supported for API key authentication. |
byMe was sent with an API key. |