Audit · Reference
API
One endpoint takes your app's events, in batches of up to 500. The Laravel SDK uses it; any other app can too, with the same server secret.
Base URL
https://services.my-beast.com/auditThe same address as MYBEAST_AUDIT_URL, over HTTPS. Requests and answers are JSON in UTF-8.
Authentication
Every request carries the project's server secret:
Authorization: Bearer sk_…The secret opens Audit for its project only, and writes into that project's log. Make one in the project's Settings in Audit, under Audit keys; it is shown once. A secret that is revoked, or isn't Audit's, is answered 401.
Server side only
Send events
/v1/eventsThe body is {"events": [ … ]} with 1 to 500 events. Send it as Content-Type: application/json, packed with gzip if you like (Content-Encoding: gzip).
{
"events": [
{
"event_id": "01a17d00-0000-7000-8000-000000000001",
"occurred_at": "2026-10-27T09:15:02.120Z",
"action": "customer.viewed",
"actor": { "type": "operator", "id": "op_7" },
"target": { "type": "customer", "id": "4512" },
"subject": "u_88231",
"account": "ws_301",
"request_id": "01JBX4Q2M8T0F3V6R9C2D5H7KA",
"reason": "Support ticket 7781: the tenant asked why their rent went up.",
"context": { "fields": ["iban", "address"], "via": "console" }
},
{
"event_id": "01a17d00-0000-7000-8000-000000000002",
"occurred_at": "2026-10-27T09:15:40.004+03:00",
"action": "identity-code.viewed",
"actor": { "type": "person", "id": "u_1001" },
"target": { "type": "person", "id": "u_88231" },
"account": "ws_301"
}
]
}curl https://services.my-beast.com/audit/v1/events \
-H "Authorization: Bearer $MYBEAST_AUDIT_SECRET" \
-H "Content-Type: application/json" \
--data @batch.jsonAudit checks each event on its own. It adds the ones it takes to the project's chain in the batch's order, and names each one it refuses with the reason. A refused event doesn't stop the others.
The event
Ids only, never names or email addresses: an email address anywhere in an event refuses it. A field the event doesn't have, such as name, refuses it too.
Fields
event_idstringrequiredYour id for the event, a UUID. Sent again, it is kept once: the answer says
duplicate. The SDK makes a UUID v7.occurred_atstringrequiredWhen it happened, by your server's clock: RFC 3339 with its offset, like
2026-10-27T09:15:02.120Z. Kept to the millisecond, in UTC. At most 5 minutes ahead of the service's clock, and not before the year 2000.actionstringrequiredWhat happened, in your words: lower-case letters and digits joined by
.,_or-, at most 80 characters. For examplecustomer.viewedorkey.revealed.actorobjectrequiredWho did it.
typestringrequiredperson(one of your users),operator(your staff) orsystem(a job, a command).idstringrequiredYour id for them: 1 to 64 letters, digits,
.,_,:or-.
targetobject or nulloptionalWhat it was done to.
typestringrequiredIts kind, written like an action, at most 40 characters.
personputs the event in that person's access report.idstringrequiredYour id for it, as an actor's id.
subjectstring or nulloptionalThe person the data is about, by your id. Their access report lists the event.
accountstring or nulloptionalThe account it happened in, by your id: a workspace, an organisation, a customer.
request_idstring or nulloptionalYour request it was recorded in, the id your log lines carry. The event's page then opens that request's lines in Logs and its errors in Errors.
reasonstring or nulloptionalThe operator's reason, when you asked for one. At most 500 characters.
contextobject or nulloptionalAnything else, as an object: at most 4 levels deep and 8 KB as JSON.
The response
200 with what became of each event. kept and duplicate are both in the log: a sender may forget them. A refused event never will be as it is.
{
"kept": 1,
"duplicates": 0,
"refused": 1,
"results": [
{ "index": 0, "event_id": "01a17d00-0000-7000-8000-000000000001", "status": "kept", "seq": 1041 },
{
"index": 1,
"event_id": "01a17d00-0000-7000-8000-000000000002",
"status": "refused",
"reason": "occurred_at is in the future: check the host's clock."
}
]
}Fields
keptintegerHow many events of the batch were added to the log.
duplicatesintegerHow many were in the log already, by their
event_id.refusedintegerHow many were refused.
resultsarray of objectsOne result per event, in the batch's order.
indexintegerThe event's place in the batch, from 0.
event_idstring or nullThe event's
event_id, in lower case; null when it had none that could be read.statusstringkept,duplicateorrefused.seqintegerThe event's number in the project's chain, from 1. With
keptandduplicate.reasonstringWhy the event was refused. With
refusedonly.
Errors
When the request isn't a batch Audit can take, it answers with one of these and keeps nothing of it. The body has a message saying why.
| Status | When | The sender |
|---|---|---|
| 401 | No active server secret of Audit matches the Authorization header. | Keep every event and check the secret. |
| 402 | The project's subscription has ended. The body has type: subscription_ended and a link to renew it. | Keep every event: they can be sent once the subscription is renewed. |
| 403 | The project does not have Audit in its packages. | Keep every event and add Audit to the project at id. |
| 413 | More than 500 events, more than 2 MB as sent, or more than 8 MB unpacked. | Send the same events in smaller batches. |
| 415 | A Content-Encoding other than gzip or none. | Fix the sender. |
| 422 | Not a batch: not JSON, no events list, or an empty one. Nothing was kept. | Fix the sender. |
| 429 | More than 600 requests a minute with one server secret. | Keep every event and try again later, in the same order. |
| 5xx, or no answer | The service is down or could not answer. | Keep every event and try again later, in the same order. |
Writing your own sender
The Laravel SDK does all of this for you. An app in another language keeps the same promises if it follows the same rules:
- Keep an outbox. Write each event to a table in your own database, in the transaction of the action it records. Send from that table, never from the request.
- Give each event its id once. Make the
event_idwhen you record the event and keep it, so a batch sent again is kept once. - Send in order. Oldest first, one batch at a time, from one sender: the chain takes events in the order they arrive.
- Act on each result. Forget
keptandduplicate; setrefusedaside with its reason for a person to look at. - Keep everything when the answer isn't
200. Try again later from the same oldest event; after a413, with smaller batches.
Send events to Audit says what the SDK does with each answer, and the source of mybeast/audit-laravel is a working example.