Box
Send events Built
One HTTPS request with two headers. No SDK required, no agent to install, and nothing to rename before you start.
Your first event
export ENTRAIL_SECRET='...' # keep it out of shell history and argv
curl -sS https://<your-box>/v1/events \
-H "content-type: application/json" \
-H "x-entrail-access-id: <your access id>" \
-H "x-entrail-secret: $ENTRAIL_SECRET" \
-d '{
"action": "record.update",
"actor.type": "user",
"actor.id": "u_1043",
"outcome": "success",
"ts": "2026-09-26T02:33:02Z",
"event.description": "changed the amount on invoice 88123"
}'{ "status": "accepted",
"event_id": "01a0db8f-15ab-7235-85bc-bd56dc964dd0",
"received_at": "2026-09-26T02:33:02.635Z",
"payload_sha256": "89c0e2a9bd699bcf…6728",
"contract_version": "1.0.0-rc.5",
"seal_eta_seconds": 120 }Keep payload_sha256. It is our hash of the exact bytes you sent, and you can compute it yourself. If the two ever disagree, something changed your bytes in transit and you will know without asking us.
202 means accepted and on disk. It does not mean sealed; that follows within about seal_eta_seconds.
The six fields
Five are required. The description is required by default and a project can turn it off.
| Field | Rule |
|---|---|
| action | Dotted, such as record.update. The entrail. prefix is reserved. |
| actor.type | user · service_account · machine · db_account · ai_agent · api_key · system |
| actor.id | Any non-blank identifier, no control characters. |
| outcome | success · failure · denied · error · partial · pending |
| ts | RFC 3339. Your claim about when it happened. |
| event.description | Free text. |
Everything else is optional, and the contract has about 140 fields: severity, correlation id, session, target, actor email, the AI fields and more. Fields we do not know are kept, not dropped. They are sealed with the rest and stay queryable, just unanalysed, so you never have to strip anything before sending.
About your timestamp
It is sealed exactly as you send it and never rewritten. Chain order, though, is ingest order, never your timestamps. That is deliberate: it removes any argument about whose clock was right. Your timestamp is recorded as a claim, and the box's own timestamps sit beside it.
Batches
Up to 1000 events or 5 MiB per request, with events as the body's only key. A single event may be up to 256 KiB. Each event is judged on its own, so one bad event does not reject the batch.
Getting the receipt
curl -sS https://<your-box>/v1/events/<event_id>/receipt \
-H "x-entrail-access-id: <your access id>" \
-H "x-entrail-secret: $ENTRAIL_SECRET"You get 404 until it is sealed, so poll every few seconds. There is no deadline: a receipt you did not collect at the time is still collectable later. What to do with it is in verify a receipt.
When something is refused
| Response | Meaning |
|---|---|
| 400 malformed_json | Not parseable, including a leading byte-order mark, which is invisible and will cost you an hour. |
| 400 body_hash_mismatch | You sent your own body hash and the bytes that arrived hash differently. |
| 401 unauthorized | Bad access id or secret. It never says which. |
| 409 conflict | A client id reused with a different body. Same id and same body is a duplicate, not an error. |
| 413 | Over the event or batch size limit. |
| 422 | Failed validation. The reason names the field, never the value. |
| 503 | Not accepting right now. Retry with backoff; nothing was stored. |
Two limits are a wall clock at the proxy rather than a processing budget: ten seconds to send your headers, 120 seconds to send your body. A client that stalls mid-upload is closed rather than left hanging.
Sending your own field names
You do not have to rename anything. A mapping profile is a dictionary held on your environment, your key to our field, and you send your native format untouched with an x-entrail-source header. Values are never altered, only keys are mapped, and the raw payload is sealed byte for byte regardless.
Send us a sample of real or realistic log lines and we will write the profile. Improving it later re-derives the readable projection from the sealed bytes, so a better mapping applies backwards across everything you have already sent. Nothing is ever re-sealed.