Skip to content
bucker

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-Limits in 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.