axiom drain posts to one Axiom dataset’s ingest endpoint. Each record becomes one event with _time, level, message, and the whole Lua record under lua — which Axiom flattens at ingest, so lua.eventName and lua.attributes.* are queryable exactly as the event schema spells them.
Vendor destinations are opened per deployment. Where
axiom is not open yet, a create answers 422 DRAIN_TYPE_UNAVAILABLE and the message names the types that are. It may not be enabled on your deployment yet.- An Axiom dataset.
- An API token with ingest permission on it.
What to enter
.axiom.co. That is a suffix rule, so api.eu.axiom.co is admitted and evil-axiom.co is not — the leading dot is what stops a lookalike domain matching. Anything else answers 422 DRAIN_ENDPOINT_NOT_ALLOWED.
The last path segment may contain letters, digits, and . _ ~ % = + - only; anything else is refused rather than interpolated.
How ownership verification works
401 means the token is wrong or lacks ingest permission; a 404 usually means the dataset in the URL does not exist. Verification is limited to 5 attempts per drain per hour.
Axiom deliveries are not signed.
What arrives
A JSON array of events:_time is the one reserved field, and sending it is what makes a replayed backlog readable: without it Axiom stamps ingestion time, and a batch delivered after a pause would all land at the same instant. A timestamp that does not parse is passed through untouched rather than replaced with “now” — Lua does not assert a time it never measured.
level and message are not reserved, but they are what Axiom’s own dashboards look for.
Useful queries
Caps
A record over 256 KiB has its
body cut and suffixed ... [truncated by lua] rather than being dropped.
The 448 KiB batch cap is roughly half what the other vendor presets use, and deliberately so: an Axiom event carries the body twice — once as
message, once inside the nested record — so a full batch encodes to about 1.9× the bytes the cap was checked against. Halving the cap is what keeps a full batch inside a 1 MiB request.Prefer OTLP? Use an otlp drain
Axiom accepts OTLP directly, and an otlp drain reaches it with no preset of its own: point it at Axiom’s OTLP logs endpoint and add X-Axiom-Dataset as a drain header.
lua.* shape the native preset produces.
Test the token with curl
200 means accepted; the event shows up in the dataset within a few seconds. Then:
If it isn’t working
422 DRAIN_VALIDATION_FAILED on the endpoint
422 DRAIN_VALIDATION_FAILED on the endpoint
The URL does not end in a dataset. It must be the full ingest URL:
https://api.axiom.co/v1/ingest/<dataset>.422 DRAIN_ENDPOINT_NOT_ALLOWED
422 DRAIN_ENDPOINT_NOT_ALLOWED
The host does not end in
.axiom.co. A self-hosted proxy in front of Axiom is not accepted by this type — use a generic HTTPS drain for that, which is signed as well.Everything lands at the same timestamp
Everything lands at the same timestamp
That is ingestion time, which means
_time was not read. Lua always sends it, so check for an ingest transform in Axiom that overwrites it.401 on every delivery
401 on every delivery
The stored value must read
Bearer <token>, prefix included — the header is sent verbatim. Re-enter it with lua drains update <id> --header Authorization.Next steps
Event schema
What every field under
lua means.OpenTelemetry
The OTLP alternative, and what it trades.
Protecting your destination
What verification does and does not prove.
lua drains
Every verb and flag.

