The ingest wire protocol
This is the specification for the bytes a client sends to Bucker. It exists so that a third party can implement a client without reading Bucker's source, and so that the compatibility claim below can be checked rather than believed.
Sentry SDK compatibility, stated plainly
The envelope format is deliberately Sentry-envelope compatible. An unmodified Sentry SDK pointed at a Bucker DSN works. Changing the DSN is the only change required.
This is a design decision rather than a coincidence, and it is the same migration path GlitchTip and Bugsink took. Three things make it hold, and all three are load-bearing:
- The grammar below is Sentry's newline-delimited envelope grammar.
- All three historical credential placements are accepted, so an SDK of any vintage authenticates.
- A 429 carries
X-Sentry-Rate-Limitsin Sentry's own advisory format, which real SDKs parse to back off per category rather than retry-storming.
If you are evaluating Bucker and already run a Sentry SDK, you do not need to install anything. Point the DSN at Bucker and your existing instrumentation reports here.
Endpoint
POST https://<region>.<ingest-host>/api/<projectId>/envelope/
The trailing slash is optional; both forms are registered. <region> and <ingest-host>
come from the DSN, which has the shape:
https://<publicKey>@<region>.<ingest-host>/<projectId>
A DSN carrying a password is refused. The public key is public by construction — it ships in every browser bundle — and it authenticates ingest only. It is never an API credential.
Authentication
Any one of these. They are checked in this order, and the first present wins:
| Placement | Form |
|---|---|
| Header | X-Sentry-Auth: Sentry sentry_version=7, sentry_key=<publicKey> |
| Header | Authorization: DSN <full dsn> or Authorization: Sentry sentry_key=<publicKey> |
| Query | ?sentry_key=<publicKey> |
The query form exists because a browser cannot always set a custom header on a beacon or a
sendBeacon call. The key must belong to the <projectId> in the path; a key valid for a
different project is refused rather than silently accepted.
Grammar
Newline-delimited JSON. One envelope header, then any number of item header and payload pairs:
{"event_id":"...","sent_at":"...","dsn":"..."} <- envelope header
{"type":"event","length":123} <- item header
{...payload...} <- item payload
{"type":"repro_capsule"} <- another item header
{...payload...}
An item header may declare length in bytes. When it does, exactly that many bytes are read
as the payload and the newline that follows is consumed. When it does not, the payload runs
to the next newline. Declaring the length is what allows a payload to contain newlines.
Item types
Understood today: event, transaction, session, sessions, attachment,
client_report, replay_event, replay_recording, profile, log, and two Bucker
extensions, repro_capsule and state_patches.
An unknown item type is forwarded, not rejected. This is a guarantee, not an implementation detail: it is what lets Bucker add item types without breaking Sentry SDK clients, and what lets a Sentry SDK send item types Bucker does not yet process without losing the rest of the envelope. Do not write a client that depends on an unknown type being an error.
Compression
Content-Encoding: gzip (or x-gzip), deflate or br on the request body. Decompression is bounded:
a body that expands past the envelope ceiling is refused with a too_large outcome rather
than expanded. Sending compressed is recommended and is what most SDKs do by default.
Limits
| Limit | Value | On exceeding |
|---|---|---|
| Envelope body | 20 MiB | Whole envelope refused |
| Single item | 1 MiB | That item dropped, envelope still processed |
| Items per envelope | 1000 | Whole envelope refused |
client_report entries |
100 | Excess entries ignored |
client_report quantity |
100,000 | Clamped |
Individual event fields are bounded too — message and exception values at 8192 bytes, stack
frame fields at 4096, tag values at 1024, with bounded counts on frames, breadcrumbs,
exceptions, tags and context keys. Fields are truncated rather than rejected, because
dropping a customer's whole event because one breadcrumb was long is the worse failure. What
was truncated is recorded on the stored event under extra.__bucker_truncations, so a
missing tail is visible rather than mysterious.
An oversized single item is dropped and accounted for, not silently discarded — see outcomes below.
Responses
| Status | Meaning |
|---|---|
200 |
Accepted. Body is {"id": "<eventId>"}. |
400 |
The envelope did not parse, or exceeded a hard limit. |
401 |
The key is missing, malformed, or not valid for this project. |
413 |
The body exceeded the envelope ceiling, before or after decompression. |
429 |
Rate limited or over quota. See the header below. |
Acceptance means the envelope is durably enqueued, not that it has been processed. Grouping, scrubbing and symbolication happen afterwards, off the request path.
Rate limit headers
A 429 carries Sentry's advisory format:
X-Sentry-Rate-Limits: <retry_after_seconds>:<categories>:<scope>
Categories are the data categories below. A client that parses this backs off for the named categories only, which is why the header is worth honouring rather than treating a 429 as uniform.
Data categories and drop accounting
Every item maps to a category — error, transaction, session, attachment, replay,
profile, log, internal — and every drop is recorded against one. The recorded
outcomes are rate_limited, quota_exceeded, too_large, invalid and sampled.
This matters to a client author: silent data loss is the complaint this category of product earns most often, so a drop here is always attributable. If you send something and it does not appear, there is a row saying which category it belonged to and why it went. Ask for it rather than guessing.
Client reports
An SDK reports its own drops with a client_report item, so events discarded before they
reached the network are still counted. Entries are capped at 100 per envelope and each
quantity is clamped; a client that exceeds either is not an error, but the excess is not
counted. Client reports are metered like any other category.
OpenTelemetry
Separate surface, separate document: OTLP traces and logs are accepted at /v1/traces and
/v1/logs. See otlp.md for encodings, headers and the environment variables a
stock OTel exporter needs.
What this document does not cover
The payload schema of an event — the fields inside it — is Sentry's event schema, and this
document does not restate it. What is written here is the envelope layer: framing, auth,
limits, and what happens when something is refused. Where the two disagree in practice, the
envelope layer is the one Bucker guarantees.