> ## Documentation Index
> Fetch the complete documentation index at: https://docs.userkit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Read the staff audit trail

> Requires `audit:read`. What staff did inside this organization, newest first: who acted, what they did, which object they did it to, when, and from where.

Entries come from the platform's internal event bus rather than from each endpoint writing its own, which has two consequences worth knowing. An action is recorded a moment after it succeeds, never during the request that performed it. And the trail is **append-only**: no endpoint edits or removes an entry, and neither does anything else.

Recording is never conditional on a plan, and neither is keeping: every organization's trail is retained for the same period. What a plan changes is how far back this read looks, reported as `window_days` — so entries beyond it are hidden rather than deleted, and a plan that lengthens the window shows the history that was always there.



## OpenAPI

````yaml /api-reference/openapi.json get /v1/organization/audit-events
openapi: 3.1.0
info:
  title: UserKit API
  version: 1.0.0
  description: >-
    The HTTP surface of UserKit.


    Two planes share one API. The **staff plane** is what a human uses in the
    panel: users, organizations, roles, members, keys. The **customer plane** is
    what a developer's own product uses: contacts, identities, hosted or
    federated sign-in.


    Every error answers the same envelope — `{"error": {"code", "message"}}`.
    The `code` is a stable contract to branch on; the `message` is for a person
    and may change.
servers:
  - url: '{baseUrl}'
    description: The API host.
    variables:
      baseUrl:
        default: https://api.userkit.dev
        description: Base URL of the API, no trailing slash.
security:
  - sessionToken: []
tags:
  - name: Authentication
    description: >-
      Public sign-up, sign-in, two-factor and password recovery for staff
      accounts.
  - name: Session
    description: >-
      The active session: who the caller is, which organization they are in, and
      signing out.
  - name: Account
    description: The caller's own account — profile, password, sessions, avatar.
  - name: Two-factor
    description: >-
      TOTP setup, activation and recovery codes. Returns 501 when two-factor is
      unavailable on the server.
  - name: Organizations
    description: The organizations a user belongs to, and the active one.
  - name: Environments
    description: >-
      The live and test environments seeded with every organization, and their
      identity settings.
  - name: Members
    description: Memberships and invitations.
  - name: Roles
    description: Roles and the permission catalogue they draw from.
  - name: Audit log
    description: >-
      What staff did inside an organization. Append-only, and read behind its
      own permission.
  - name: API keys
    description: Secret keys (`uk_sk_…`) and publishable keys (`uk_pk_…`).
  - name: Contacts (staff session)
    description: >-
      The staff view of the customer plane, opened by a staff session. Reads
      take `?environment=` as an explicit view parameter.
  - name: Contacts (API key)
    description: >-
      The machine surface, authenticated by an API key. The environment is the
      key's environment and cannot be named by the caller.
  - name: Customer plane
    description: >-
      Called from the developer's own pages with a publishable key: boot, magic
      link, and the contact's own session.
  - name: Hosted auth
    description: >-
      Sign-up, sign-in, verification and recovery for contacts, when UserKit
      owns the account.
  - name: Customer teams
    description: >-
      A customer is a team. Its roster, its invitations and its two roles —
      owner and member — administered by the contact's own session. The active
      customer travels in `X-Customer-Id`.
  - name: Webhooks
    description: >-
      Outbound webhooks: endpoints, the published event catalogue, the delivery
      log, replay and test sends. Never gated by a plan — webhooks are a
      developer primitive.
  - name: Catalogue
    description: >-
      The plans, prices and features **you** sell to your own customers. Per
      environment, a price per currency, and `recurring` or `one_time`. Distinct
      from the plan you are on with UserKit, which is `GET
      /v1/organization/entitlements`.
  - name: Subscriptions
    description: >-
      What one of **your** customers is paying you, mirrored from the gateway
      that charges them. The gateway is the truth about money — it holds the
      schedule, runs the retries and decides what a proration is worth — so
      these routes ask it to change something and answer with what it then said.
      `provider_synced_at` is how stale the copy admits to being.
  - name: Provider webhooks
    description: >-
      Where a payment provider delivers to. Not a surface you call — it is a URL
      you paste into the provider's dashboard, which is why it sits outside
      `/v1`: a version bump must never mean editing a setting in somebody else's
      product. Signed with the secret of the connection named in the path, and
      safe to retry.
  - name: Entitlements
    description: >-
      What one of **your** customers may do, resolved: the plan their
      subscription carries, plus the overrides you promised them on top. This is
      the read your own gate calls, so it is cached and answers in one round
      trip. It is a different question from `GET /v1/organization/entitlements`,
      which is the plan **you** are on with UserKit — two catalogues, same word,
      different money.
paths:
  /v1/organization/audit-events:
    get:
      tags:
        - Audit log
      summary: Read the staff audit trail
      description: >-
        Requires `audit:read`. What staff did inside this organization, newest
        first: who acted, what they did, which object they did it to, when, and
        from where.


        Entries come from the platform's internal event bus rather than from
        each endpoint writing its own, which has two consequences worth knowing.
        An action is recorded a moment after it succeeds, never during the
        request that performed it. And the trail is **append-only**: no endpoint
        edits or removes an entry, and neither does anything else.


        Recording is never conditional on a plan, and neither is keeping: every
        organization's trail is retained for the same period. What a plan
        changes is how far back this read looks, reported as `window_days` — so
        entries beyond it are hidden rather than deleted, and a plan that
        lengthens the window shows the history that was always there.
      operationId: listStaffAuditEvents
      parameters:
        - $ref: '#/components/parameters/OrganizationHeader'
        - name: before
          in: query
          required: false
          schema:
            type: integer
            format: int64
          description: >-
            The `sequence` of the oldest entry you already hold. Paging by
            sequence keeps a page stable while new actions land above it.
      responses:
        '200':
          description: One page of the trail, newest first.
          content:
            application/json:
              schema:
                type: object
                properties:
                  events:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        sequence:
                          type: integer
                          format: int64
                          description: Global and monotonic. Also the paging cursor.
                        type:
                          type: string
                          description: >-
                            What happened, as `aggregate.fact` — `role.updated`,
                            `api_key.revoked`, `member.role_changed`. New kinds
                            appear over time; treat an unfamiliar one as an
                            entry you cannot label rather than as an error.
                        actor:
                          type: object
                          description: >-
                            Who did it. The address is the one the account
                            carried at the time, so the entry still answers
                            "who" after the account is gone.
                          properties:
                            user_id:
                              type: string
                              format: uuid
                            email:
                              type: string
                            ip:
                              type: string
                            user_agent:
                              type: string
                        target:
                          type: object
                          description: What was acted on.
                          properties:
                            type:
                              type: string
                            id:
                              type: string
                              format: uuid
                        payload:
                          type: object
                          description: >-
                            Detail of the action, shaped by its type.
                            Identifiers, never credentials: a key's prefix
                            appears here, a key never does.
                        occurred_at:
                          type: string
                          format: date-time
                  has_more:
                    type: boolean
                    description: Whether older entries exist below this page.
                  window_days:
                    type:
                      - integer
                      - 'null'
                    format: int64
                    description: >-
                      How far back this read looked, in days, as the plan
                      allows. `null` means it looked through everything the
                      trail still holds. Entries older than the window are
                      retained and unreadable, never removed — the trail is kept
                      for the same period on every plan.
              example:
                events:
                  - id: …
                    sequence: 4821
                    type: member.role_changed
                    actor:
                      user_id: …
                      email: ana@acme.dev
                      ip: 203.0.113.7
                      user_agent: Mozilla/5.0 …
                    target:
                      type: membership
                      id: …
                    payload:
                      role_id: …
                      changed_by: …
                    occurred_at: '2026-07-29T14:02:11Z'
                has_more: false
                window_days: 30
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
components:
  parameters:
    OrganizationHeader:
      name: X-Organization-Id
      in: header
      required: false
      schema:
        type: string
      description: >-
        The organization the caller is acting on — the `org_…` code that appears
        in the panel URL. It *identifies*; the membership JOIN is what
        *authorizes*, so a forged code reads nothing. Absent, the session's
        default organization answers.
  responses:
    Unauthorized:
      description: >-
        `unauthorized` — missing, malformed or expired credential. A well-formed
        credential from the wrong family says so: "this endpoint expects a staff
        session token, not an organization API key".
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: '`forbidden` — your role does not allow this action.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    Error:
      type: object
      description: Every error in this API answers this envelope.
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: Stable. Branch on this.
            message:
              type: string
              description: For a person. May change.
          required:
            - code
            - message
      required:
        - error
      example:
        error:
          code: forbidden
          message: your role does not allow this action
  securitySchemes:
    sessionToken:
      type: http
      scheme: bearer
      description: >-
        A staff session token, `uk_st_…`. Minted by sign-up, sign-in or the
        two-factor exchange.

````