コンテンツにスキップ

リファレンス 読了目安 7 分

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"
          }
        }
      ]
    }
  }
}
  • id increases with every new event. Cursor mode orders and pages by it.
  • event is a sentence that describes the action, in English.
  • action is the kind of the event. See Event kinds.
  • ipAddress is the client IP of the action. It is null when the IP is unknown, for actions made through MCP, and for events recorded before IPs were stored.
  • organisation is null for account events that do not belong to an organisation.
  • organisationUser is null for actions made with an API key. role is 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_REFUSED and MFA_FAILED are 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
    }
  }
}
  • numberElements is the page size, 15 by default.
  • orderBy is one of ActionId, CreatedTime, OrganisationUser and Organisation, and sort is Asc or Desc. Without orderBy, events come newest first and sort has no effect.
  • Leave out page to get every matching event in one response. pageInfo is then null. 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
    }
  }
}
  • afterId returns only the events with a greater id. Omit it to start from the oldest event.
  • first is the page size. It is 100 by default and at most 1000. A larger value returns 1000 events.
  • hasMore is true when more events follow the last one returned.
  • nextAfterId is the id of the last returned event. When no event is returned, it is the afterId you sent, so the cursor never moves back. It is null only when you sent no afterId and 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.

  1. First call. Query auditActions with first and without afterId.
  2. Store the cursor. Save nextAfterId from the response.
  3. Next call. Pass the saved value as afterId. Events come back in increasing id order.
  4. Page through a backlog. While hasMore is true, repeat the call with the new nextAfterId.
  5. Idle poll. When no event is returned, nextAfterId is the afterId you 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.