Ingest
Envelope, legacy store, release artifacts, and user feedback, all DSN-authenticated.
Core ingest path. Authenticate with DSN credentials. Dashboard sessions are not valid here.
What is ingest?
A DSN-authenticated POST to /api/{project_id}/envelope/ or /store/. Success is 202 { "id": "<uuid>" } with a 2 MB body cap.
Base: POST /api/{project_id}/… where {project_id} is the project UUID from the DSN. Persist runs after the response.
Try ingest
Build a store POST for a host you already run. Seed defaults match ./scripts/seed-dev.sh. Change host if EPURE_PORT is not 8080. Fields persist in this tab.
store curl
curl -sS -D - -o /tmp/epure-ingest.json -X POST
"http://localhost:8080/api/550e8400-e29b-41d4-a716-446655440000/store/"
-H "X-Sentry-Auth: Sentry sentry_version=7, sentry_key=a1b2c3d4e5f6g7h8i9j0, sentry_secret=supersecretdevkey"
-H "Content-Type: application/json"
-d '{
"platform": "javascript",
"exception": {
"values": [{ "type": "Error", "value": "Epure try-it" }]
}
}'Expected
HTTP/1.1 202 Accepted
{ "id": "<event-uuid>" }
Then open Issues. This panel does not POST from the browser. CORS and your DSN stay on your machine.
POST /api/{project_id}/envelope/
Accepts application/x-sentry-envelope (newline-delimited Sentry envelope).
Request
| Auth | X-Sentry-Auth or sentry_key / sentry_secret query |
| Body | Envelope bytes ≤ 2 MB |
| CORS | OPTIONS supported |
Envelope layout:
- Envelope header JSON line (may include
event_id,sdk, …) - Repeated: item header JSON (
type, optionallength) + newline + payload
Item handling
Item type | Behavior |
|---|---|
event | Parsed, queued, persisted |
transaction | Discarded (exception-only product) |
attachment, session, replay_*, profile*, check_in, other | Skipped |
If no event item is present → 400 invalid_envelope.
Response
202 Accepted
{ "id": "660e8400-e29b-41d4-a716-446655440001" }id is the assigned event UUID. Persistence runs on a Tokio mpsc worker after the response.
From the repo root after seed-dev:
curl -sS -D - -o /tmp/epure-envelope.json -X POST \
"http://localhost:8080/api/550e8400-e29b-41d4-a716-446655440000/envelope/" \
-H "X-Sentry-Auth: Sentry sentry_version=7, sentry_key=a1b2c3d4e5f6g7h8i9j0, sentry_secret=supersecretdevkey" \
-H "Content-Type: application/x-sentry-envelope" \
--data-binary @fixtures/sentry/browser/envelope.txtHTTP/1.1 202 AcceptedPOST /api/{project_id}/store/
Legacy single-event JSON ingest (older SDKs, curl, microcontrollers).
Request
| Auth | DSN (header or query) |
| Body | JSON event, or gzip/zlib-compressed JSON (Content-Encoding) |
| Limit | ≤ 2 MB |
Response
202 Accepted with { "id": "<uuid>" } (same shape as envelope).
Malformed / empty / bad compression → 400 invalid_store.
curl -sS -D - -o /tmp/epure-store.json -X POST \
"http://localhost:8080/api/${PROJECT_ID}/store/" \
-H "X-Sentry-Auth: Sentry sentry_version=7, sentry_key=${PUBLIC}, sentry_secret=${SECRET}" \
-H "Content-Type: application/json" \
-d '{"platform":"javascript","exception":{"values":[{"type":"Error","value":"boom"}]}}'POST /api/{project_id}/releases/{version}/files/
Upload release artifacts for JS/TS demangle. Walkthrough: Source maps.
Request
| Auth | DSN |
| Content-Type | multipart/form-data |
| Fields | file (required), name (optional path) |
{version} | Release label; no .., /, or \ |
Response
201 Created
{ "id": "<artifact-uuid>", "name": "~/dist/app.min.js.map" }| Status | error | What to do |
|---|---|---|
400 | invalid_release_version | Drop .. / \ from {version} |
400 | invalid_release_file | Send multipart field file |
401 | invalid_dsn | Fix key |
403 | dsn_revoked / project_mismatch | Rotate key or match project id |
500 | storage_error | Check EPURE_ARTIFACTS_DIR volume |
Files land under EPURE_ARTIFACTS_DIR (canonical-path sandbox).
POST /api/{project_id}/user-feedback/
Crash-dialog feedback linked to an ingested event.
Request body
{
"event_id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Ada",
"email": "ada@example.com",
"comments": "Clicked Save and it crashed"
}| Field | Required |
|---|---|
event_id | yes; must exist for this project |
name, email, comments | no |
Email values are scrubbed with the same secret rules as event ingest.
Response
201 Created → { "id": "<feedback-uuid>" }
Unknown event_id → 404 event_not_found. Read it back: Events.
Async path and spike valve
After DSN + ingest-cap checks:
- Fingerprint preview from the event payload.
- Spike valve (default 100 events/min/fingerprint): enqueue full body or counter-only (body discarded; issue count still increments). Both still return
202. - Worker demangles JS/TS when maps exist, scrubs PII, groups, writes Postgres.
Per-project hard cap (default 5000 events/hour) → 403 ingest_cap_exceeded before enqueue.
Queue unavailable → 503 queue_unavailable.
Details: Errors.
Can I GET an event with the DSN?
What does HTTP 202 mean?
Accept is non-blocking. Persist and JS/TS demangle run on a Tokio mpsc worker after the response. Spike-valve saturation still returns 202 while dropping duplicate bodies. Empty Issues after a 202 is usually the environment filter: Errors.
What is the 2 MB cap?
Envelope and store bodies over 2 MB return 413 payload_too_large, and release file uploads share that cap. {version} cannot contain .., /, or \. This is separate from the hourly ingest cap, which returns 403.
What does the spike valve do?
About 100 raw events per minute per fingerprint. Saturated fingerprints still get 202 and a counter bump, while extra bodies may drop. The hourly project cap (default 5000) is the 403 ingest_cap_exceeded path, checked before enqueue. A closed queue is 503.