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
accountIdslists 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.actorTypesays who:USERfor a person,AGENTfor the Billabex agent,SYSTEMfor an internal path.actorChannelis set for a person and says which door they came through:WEBAPP,PUBLIC_APIorMCP.actorSourceis set for a system actor:CONNECTOR,SCHEDULER,WEBHOOK,ADMIN_API,MIGRATION,WORKFLOWorINTERNAL.actorDeletedistrueonly for a user known to have been deleted. A missing name withactorDeleted: falsemeans the projection has not caught up yet, not that the person is gone.metadatais the serialized command input, withpassword,secretandtokenvalues 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.