Audit trail
Every authenticated call you make to the Staff & Go API is recorded. GET /api/v1/client/audit/entries
returns that record — yours, and nothing else.
It is what lets you answer your customer when they ask, under GDPR, what you did on their behalf and when.
What "yours" means
The trail is bound to the credential you authenticate with — the OAuth client — not to your partner account as a whole. If you hold several clients (one per environment, one per customer, a separate one for a migration), each has its own trail and none of them reads another's, even when two of them act for the same company.
Because the binding is the client and no longer a single company, a credential mandated on several establishments now gets its trail too. It used to be refused, having no one company to bind to.
The companies you are mandated on today are still part of it. A credential reads the calls it made for the establishments it is mandated on now — not the ones it was mandated on before. If we narrow a credential's scope at your customer's request, the calls it made for the company it no longer covers leave its trail on the spot, rather than lingering for the retention period. Widen it back and they return: nothing is deleted, only bounded.
One consequence worth knowing: calls made while a credential was mandated on several establishments are recorded without a company — there is no single one they were made for. Those stay in that credential's trail whatever its scope becomes later. They are its own calls, and there is nothing on them to match a company against.
One exception, and it is temporary. Calls recorded before this binding existed carry only the company they were made for, not the credential that made them. Those older lines are returned to any of your credentials mandated on that company — so if two of your clients work for the same customer, each still sees the other's older calls. Nothing crosses over to another partner, and everything recorded from now on is separated per credential. The missing value cannot be reconstructed after the fact, so those lines are not being relabelled: they age out with the retention period.
A credential that carries neither is refused (403): no client identifier — a token issued before the
identifier was rolled out — and several establishments leaves nothing to bind the trail to, and we would
rather refuse than answer with someone else's activity mixed in. Ask us to roll the identifier out on that
client and the trail opens, with no change on your side.
What the trail holds
Metadata only: when the call happened, the verb, the path, the response status, how long it took, the idempotency key, and a SHA-256 hash of the request body.
The body itself is never kept, nor are cookies, nor the Authorization header. The hash is enough to prove
which payload was submitted without keeping a byte of it — useful for settling a doubt about an import:
hash the file you believe you sent and compare.
Calling it
Required scope: audit-entries:read.
curl -H "Authorization: Bearer $TOKEN" \
"https://api.staffngo.com/api/v1/client/audit/entries?from=2026-07-01T00:00:00Z&to=2026-07-31T23:59:59Z&method=POST"
{
"items": [
{
"traceId": "0af7651916cd43dd8448eb211c80319c",
"occurredAt": "2026-07-28T09:14:22.104Z",
"method": "POST",
"path": "/api/v2/client/app/payroll/distribution/trigger",
"statusCode": 200,
"durationMs": 431,
"bodyHash": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
"idempotencyKey": "2026-07-payroll-run-3"
}
],
"nextCursor": "MTQ4Mg=="
}
Filters
| Parameter | Effect |
|---|---|
from, to |
Bounds on the call time, ISO-8601 UTC. At most 31 days apart. |
method |
HTTP verb, e.g. POST. |
statusCode |
Response status, e.g. 200. |
limit |
Page size. Defaults to 50, capped at 200 — a larger value is brought down to the cap, not rejected. |
cursor |
Continuation token, taken verbatim from the previous page's nextCursor. |
An omitted parameter filters nothing. A from/to window wider than 31 days is refused (400) rather than
silently narrowed: believing you read a year when you read a month is worse than an error.
Paging
Calls come back newest first. While nextCursor is not null there is another page: repeat the same
URL with cursor=<nextCursor> appended.
The cursor is opaque — do not decode it and do not build one. It is a keyset token, not an offset: the trail grows while you page through it, and an offset would make you skip or re-read rows every time a new call is recorded.
Two more limits worth knowing
Anonymous calls are not in it. Badge ingestion (/badge/*) is open without a token — a physical reader
holds none. Those calls are journaled, but with no partner and no company attached, so they cannot be
returned to anyone. If your integration only does badge ingestion, your trail is empty, and that is expected.
The trail is purged. Rows older than the configured retention period disappear. Export what you need as you go, rather than at the moment someone asks for it.