enTrail

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

curl
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"
  }'
Response
{ "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.

FieldRule
actionDotted, such as record.update. The entrail. prefix is reserved.
actor.typeuser · service_account · machine · db_account · ai_agent · api_key · system
actor.idAny non-blank identifier, no control characters.
outcomesuccess · failure · denied · error · partial · pending
tsRFC 3339. Your claim about when it happened.
event.descriptionFree 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
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

ResponseMeaning
400 malformed_jsonNot parseable, including a leading byte-order mark, which is invisible and will cost you an hour.
400 body_hash_mismatchYou sent your own body hash and the bytes that arrived hash differently.
401 unauthorizedBad access id or secret. It never says which.
409 conflictA client id reused with a different body. Same id and same body is a duplicate, not an error.
413Over the event or batch size limit.
422Failed validation. The reason names the field, never the value.
503Not 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.