Platform6 min

Audit Journal

Read who did what in an organization: one entry per successful command, filterable by account, actor and free text.

Every successful command Billabex runs leaves one entry in the organization’s audit journal, whoever ran it: a person in the web app, a third-party client through this API or the MCP server, the Billabex agent, or an internal system path such as a connector synchronization.

An account’s history is not a separate resource. It is this same journal scoped to one account, which is also the only way to read the history of an account that has been deleted.

Endpoint

GET /api/public/v1/organizations/{organizationId}/audit

Requires organizations:read or organizations:all.

Parameter Type Description
accountId UUID Keep only the entries that acted on this account
actorTypes USER | AGENT | SYSTEM | SERVICE_ACCOUNT, repeatable Keep only these kinds of actor
action string Keep only this command name, for example DeleteAccount
search string Case-insensitive match in the recorded metadata, or on an audited account id
page integer Page to return, 1-based, defaults to 1
limit integer Entries per page, defaults to 20, maximum 100

Unlike the other list endpoints, this one paginates by page number rather than by cursor. The journal is append-only and read newest first, so a page number is stable enough, and it spares you a cursor for a list you mostly read the head of.

Response

{
  "nodes": [
    {
      "id": "f5183985-1f0e-4c2b-9a35-1a2b3c4d5e6f",
      "organizationId": "6effaee1-01d0-4205-b2da-911e28483dc6",
      "accountIds": ["78ce0186-2b4f-4b1c-9c3a-0d1e2f3a4b5c"],
      "actorType": "USER",
      "actorId": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
      "actorFullName": "Marie Dupont",
      "actorEmail": "marie@example.com",
      "actorDeleted": false,
      "actorChannel": "WEBAPP",
      "actorSource": null,
      "action": "DeleteAccount",
      "metadata": "{\"accountId\":\"78ce0186-2b4f-4b1c-9c3a-0d1e2f3a4b5c\"}",
      "createdAt": "2026-08-27T19:40:35.542Z"
    }
  ],
  "totalCount": 1,
  "page": 1,
  "limit": 20,
  "totalPages": 1
}

Reading an entry

  • accountIds lists every account the command acted on. A bulk action over two hundred accounts is one entry carrying two hundred ids, not two hundred entries. An organization-wide action, such as changing a setting, carries an empty array.
  • actorType says who: USER for a person, AGENT for the Billabex agent, SYSTEM for an internal path.
  • actorChannel is set for a person and says which door they came through: WEBAPP, PUBLIC_API or MCP.
  • actorSource is set for a system actor: CONNECTOR, SCHEDULER, WEBHOOK, ADMIN_API, MIGRATION, WORKFLOW or INTERNAL.
  • actorDeleted is true only for a user known to have been deleted. A missing name with actorDeleted: false means the projection has not caught up yet, not that the person is gone.
  • metadata is the serialized command input, with password, secret and token values redacted. Beyond four thousand characters it is replaced by {"truncated": true, "action": "..."}.

Finding an account that no longer exists

Once an account is deleted it is gone from every list, and its id alone tells you nothing. Its name, however, survives in the metadata of the commands that touched it. Search for the name, read the id off the CreateAccount entry, then scope the journal to that id:

# 1. Find the account by name
curl -H "Authorization: Bearer $TOKEN" \
  "$BASE_URL/api/public/v1/organizations/$ORG/audit?search=CCOG&action=CreateAccount"

# 2. Read its whole history, including who deleted it
curl -H "Authorization: Bearer $TOKEN" \
  "$BASE_URL/api/public/v1/organizations/$ORG/audit?accountId=$ACCOUNT_ID"

search also matches the audited account ids, so pasting an id finds every command that touched the account, including those whose own input never named it.

MCP

The same journal is available to an MCP client as list-audit-entries, with the same filters and the mcp:read scope.

Limits

  • The journal starts on 6 August 2026. Anything older was never recorded.
  • Writing an entry is best-effort: it never fails the command it describes, so a missing entry means unattributed, never “did not happen”.
  • Commands that only maintain derived state are deliberately excluded, as are password and OAuth token commands.