Audit entries
Every create, update and delete across the services writes an entry, as does every token mint. The audit API is how they are read.
Entry types
entryType | Written by | Organization |
|---|---|---|
Organization | Account API | Caller's |
User | Account API | Caller's |
Group | Account API | Caller's |
UserApiKey | Account API | Caller's |
TagGroup | Library set API | Caller's |
Tag | Library set API | Caller's |
VariableSet | Library set API | Caller's |
Variable | Library set API | Caller's |
LibraryImport | Library set API | Caller's |
Authentication | Auth UI | None — written before a JWT exists |
JWT | Identity API | None or the caller's, depending on when it is written |
JWTRefresh | Identity API | Caller's |
JWK | Identity API | None |
Actions
action | |
|---|---|
Create | A record was created, or a token minted |
Update | A record was changed |
Delete | A record was removed |
Refresh | A session token was renewed |
Sign | A signing operation |
Merge | A merging library import |
Replace | A replacing library import |
LinkIdentity | A second provider identity was attached to an existing account |
Logout | A session ended |
Denied | A login was refused |
ProvisionDenied | No account existed and no account pattern permitted creating one |
ApiKeyDenied | An API key named nobody, or had been revoked or had expired |
Before and after state
beforeState and afterState are JSON strings, serialised by the writing service.
| Action | State |
|---|---|
Create | afterState only |
Update | Both |
Delete | beforeState only |
Whether they are returned at all depends on the caller's permissions — see global entries below.
Global entries
Entries with an empty organization ID belong to no organization: the logins, logouts and token mints written before a JWT exists. They are only ever the business of somebody holding the global audit permissions.
| Organizational entries | Global entries | |
|---|---|---|
| Appear in the list | AuditList | AuditGlobalList |
| Before/after state visible | AuditRead | AuditGlobalRead |
AuditGlobalList widens the list to include global entries alongside the caller's own. Both global
permissions are marked global, so handing them out needs PermissionsAllGlobal.
Because they are two separate axes, a single page can list an entry and withhold what it changed. That is by design: an entry appearing at all is often the useful fact, and the state is the sensitive part.
Unauthenticated writes
POST / on the audit API is the only endpoint in the product that accepts an unauthenticated write, because
Authentication, JWT and JWK entries are written before a token exists.
Without a JWT, the entryType must appear in the service's Audit:AuthNotRequired allow-list, which ships as:
["FailedLogin", "Authentication", "JWT", "JWK"]
Anything else is 401. See Configuration.
Versions
An entry written with an ID that already exists becomes another version of it. That is how the log records
successive changes to one entity: the entries share an ID and differ by version.
DELETE /{id} removes every version, and answers with what it removed.
Import is one entry, not many
A library import writes a single entry with the action Merge or Replace, the organization as the entity, and
the counts as the after state.
Two reasons. The audit API is reached over HTTP, so a file with hundreds of variables in it would spend longer being audited than imported. And what happened was one act, which the counts describe in full.
The notes are deliberately left out of the entry. Re-importing an unchanged file is the ordinary repeat case and
produces a note per entity left alone — worth telling the caller, not worth shipping to another service as a
record of nothing having changed.
Reading the log
curl -X POST "$AUDIT_API/filter?take=25&sortField=LoggedAt&sortDescending=true" \
-H "x-tagshape-auth: $JWT" \
-H "Content-Type: application/json" \
-d '[{"field":"EntryType","operatorType":"eq","value":"Variable"}]'
Names for the loggedBy IDs come from GET /users/names on the account API, which asks for AuditList rather
than UserList for precisely this reason — whoever may read the log may see who wrote each entry.