Epure
Ops

Spike valve: what a fingerprint flood returns on a 2-container host

Self-hosted error flood soft sample: still HTTP 202 with a counter bump and body drop, then 403 ingest_cap_exceeded at the hourly project cap.

On this pageShow
  1. SaaS Spike Protection vs self-host
  2. Per-fingerprint spike valve
  3. Hourly project cap (403)
  4. Soft sample vs hard reject
  5. Validate with documented curls
  6. Failure modes under flood
  7. Limitations
  8. Questions

When the same fingerprint floods a 2-container Epure host, ingest still returns HTTP 202, bumps the issue counter, and drops excess bodies. After the hourly project cap, clients see 403 ingest_cap_exceeded.

Epure is a self-hosted exception tracker (Apache 2.0) that accepts Envelope + DSN traffic from official Sentry SDKs. The documented Docker Compose v2 stack is two services: epure and postgres (postgres:16-alpine). This ops note covers the sentry spike fingerprint rate limit path: how a self-hosted error flood soft sample behaves, why SaaS Spike Protection is missing on Sentry self-host, and how soft sample differs from a hard 429.

Authoritative defaults live in Concepts and API Errors. Sibling context: Sentry SDK compatible and Epure vs Sentry.

SaaS Spike Protection vs self-host

Sentry SaaS Spike Protection is documented as a billing-coupled, per-project spike control on cloud quotas. Sentry's self-hosted develop docs state Spike Protection is not available because it is tightly coupled with Billing Quotas.

Treat those pages as third-party / second-hand: Sentry Spike Protection and Sentry self-hosted. They explain why "sentry self-hosted spike protection missing" shows up in ops searches. They do not describe Epure's in-process fingerprint valve.

Per-fingerprint spike valve

Epure's spike valve limits raw events to about 100 per minute per fingerprint. When saturated, the client still receives HTTP 202 Accepted; the issue counter increments; excess event bodies are not stored.

Fingerprint

A fingerprint is the grouping key for an issue. By default Epure uses a SHA-256 of the normalized exception type plus the top in-app stack frame (file + function + line). A custom fingerprint array on the event overrides that rule. See Concepts.

Spike valve

The spike valve is an in-process per-fingerprint soft limiter on the Rust binary. Default: about 100 raw events per minute per fingerprint (API Errors). Soft sample means the client still gets HTTP 202, the issue event_count keeps rising, and excess duplicate bodies are not stored. Under flood, operators keep the event count without proportional row growth.

Documented Epure ingest valves (first-party defaults from API Errors / Concepts)

Spike valve

Default
~100 raw events / minute / fingerprint
Client sees
Still HTTP 202
Operator sees
Counter up; excess bodies not stored

Project ingest cap

Default
5000 events / hour / project
Client sees
403 ingest_cap_exceeded
Operator sees
Cap checked before enqueue; may alert kind: ingest_cap_hit

Body size

Default
2 × 1024 × 1024 bytes
Client sees
413 payload_too_large
Operator sees
Separate from spike valve
DimensionDefaultClient seesOperator sees
Spike valve~100 raw events / minute / fingerprintStill HTTP 202Counter up; excess bodies not stored
Project ingest cap5000 events / hour / project403 ingest_cap_exceededCap checked before enqueue; may alert kind: ingest_cap_hit
Body size2 × 1024 × 1024 bytes413 payload_too_largeSeparate from spike valve

First-party defaults from Epure Concepts and API Errors docs as of 2026-09. No Redis-backed global rate limiter in the documented install.

There is no Redis-backed global rate limiter in the documented install. Valves run in-process beside PostgreSQL 16 on the two-container host (Installation).

Hourly project cap (403)

Separately from the spike valve, Epure enforces a project ingest cap of 5000 events per hour by default. Crossing that cap returns HTTP 403 with JSON error value ingest_cap_exceeded.

The cap is checked before enqueue. Operators may see an alert with kind: ingest_cap_hit. Fix path from the docs: wait for the hour window or raise the cap in project settings (API Errors).

  • Do not conflate with dsn_revoked (also 403): rotate or recreate the key and update the DSN.
  • Do not conflate with project_mismatch (403): URL {project_id} must match the DSN project UUID.
  • Do not conflate with payload_too_large (413): shrink the store/envelope body under the 2 MB router cap.

Soft sample vs hard reject

Under fingerprint flood, Epure soft-samples: HTTP 202, counter bump, body drop. Telebugs Ingest Protection (second-hand, 1.18.0 changelog dated 2026-06-29) can hard-reject with HTTP 429 Too Many Requests before writing. Epure's hard stop for the project hour is 403 ingest_cap_exceeded.

Soft sample vs hard reject (Epure first-party; SaaS Spike Protection and Telebugs labeled second-hand)

Epure spike valve

Behavior (as documented)
Still HTTP 202; counter up; excess bodies dropped (~100 raw/min/fingerprint)
Contrast for operators
Client success under flood; storage stays bounded per fingerprint

Epure project ingest cap

Behavior (as documented)
HTTP 403 ingest_cap_exceeded at 5000 events/hour/project
Contrast for operators
Hard stop for the project hour; raise cap or wait

Sentry SaaS Spike Protection (third-party)

Behavior (as documented)
Billing-coupled dynamic per-project spike threshold; drops events to protect quota
Contrast for operators
Cloud quota framing; absent from Sentry self-hosted (Billing Quotas coupling)

Telebugs Ingest Protection 1.18.0 (third-party, 2026-06-29)

Behavior (as documented)
Global accepted-errors/min + queue/disk guardrails → HTTP 429 before writing
Contrast for operators
Hard reject vs Epure soft 202 + body drop
DimensionBehavior (as documented)Contrast for operators
Epure spike valveStill HTTP 202; counter up; excess bodies dropped (~100 raw/min/fingerprint)Client success under flood; storage stays bounded per fingerprint
Epure project ingest capHTTP 403 ingest_cap_exceeded at 5000 events/hour/projectHard stop for the project hour; raise cap or wait
Sentry SaaS Spike Protection (third-party)Billing-coupled dynamic per-project spike threshold; drops events to protect quotaCloud quota framing; absent from Sentry self-hosted (Billing Quotas coupling)
Telebugs Ingest Protection 1.18.0 (third-party, 2026-06-29)Global accepted-errors/min + queue/disk guardrails → HTTP 429 before writingHard reject vs Epure soft 202 + body drop

Cite Telebugs from their changelog (https://telebugs.com/changelog). That entry does not publish numeric RPM defaults to quote here.

Cite Telebugs from their changelog. That entry does not publish numeric RPM defaults to quote here.

Validate with documented curls

Validate ingest with the documented store or envelope curls against the Quickstart seed project. Expect HTTP 202 and a JSON body with an event id. There is no public flood script or measured counter-vs-body table in docs this week.

Compose up and health

From Quickstart / Installation:

git clone https://github.com/epure-sh/epure.git && cd epure && docker compose up

Then: curl -sS http://localhost:8080/health → expected {"status":"ok"}.

Store path (seed defaults)

Documented store POST (seed project + key from Quickstart). Expect HTTP/1.1 202 Accepted and JSON with "id": "<event-uuid>".

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"}]}}'

Envelope fixture

From repo root, Quickstart documents: 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.txt

When showing JS/Node SDKs, pin @sentry/browser@7.120.0 / @sentry/node@7.120.0 as Quickstart does. Same fingerprint twice should land on one issue with occurrence count up (Verify). Under same-fingerprint flood, keep expecting 202; treat rising event_count with fewer stored bodies as the valve working. Debug with docker compose logs epure.

Failure modes under flood

A flood that still returns HTTP 202 with a rising issue count and few stored bodies is expected spike-valve behavior, not a failed verify. The hard stop operators wait for is 403 ingest_cap_exceeded.

Cause to fix (from Verify / API Errors / Installation)

Connection refused on 8080

Error / cause
Port / compose down
Fix
docker compose ps; set EPURE_PORT if needed

401

Error / cause
invalid_dsn
Fix
Copy key from Settings → SDK connection

403

Error / cause
dsn_revoked
Fix
Create/rotate key; update dsn

403 (project)

Error / cause
project_mismatch
Fix
URL {project_id} must match DSN project UUID

403 (cap)

Error / cause
ingest_cap_exceeded
Fix
Wait for hour window or raise project cap

202, empty Issues

Error / cause
Async queue / env chip
Fix
Wait ~1s; match environment tag; docker compose logs epure

202, high count, few bodies

Error / cause
Spike valve saturated
Fix
Expected; slow unique exceptions or wait ~1 min; hourly cap is the 403 path

413

Error / cause
payload_too_large
Fix
Shrink body under 2 MB router cap
DimensionError / causeFix
Connection refused on 8080Port / compose downdocker compose ps; set EPURE_PORT if needed
401invalid_dsnCopy key from Settings → SDK connection
403dsn_revokedCreate/rotate key; update dsn
403 (project)project_mismatchURL {project_id} must match DSN project UUID
403 (cap)ingest_cap_exceededWait for hour window or raise project cap
202, empty IssuesAsync queue / env chipWait ~1s; match environment tag; docker compose logs epure
202, high count, few bodiesSpike valve saturatedExpected; slow unique exceptions or wait ~1 min; hourly cap is the 403 path
413payload_too_largeShrink body under 2 MB router cap

Limitations

Epure is exception-only on a two-container Compose stack. Spike and project valves are in-process on the Rust binary. Documented Compose installs epure and postgres only.

  • No public measured flood retention table ships in the docs: treat valve behavior as semantics (202 + counter bump + body drop), not as a bodies-kept ratio.
  • Idle RAM and TTFI figures are omitted here (dated sources conflict; remeasure before claiming).
  • Tracing, session replay, profiling, and generic logs are out of scope for Epure features.
  • Mobile symbolication (iOS/Android) is not shipped.
  • This is not protocol parity with full Sentry Cloud.
  • SaaS Spike Protection and Telebugs 429 rows above are second-hand citations only.

Use this guide if

  • You already point official Sentry SDKs at a 2-container Epure host.
  • You need the soft-sample story: still 202, counter up, excess bodies dropped.
  • You want the hard stop called out as 403 ingest_cap_exceeded at 5000/hour/project.
  • You refuse a Redis rate-limiter service in the error stack.

Read something else if

Defaults: Concepts, API Errors. Verify path: Verify. Clone: GitHub.

FAQ

Questions

What does the spike valve do on ingest?
Default ~100 raw events per minute per fingerprint. When saturated, the host still returns 202, increments the issue counter, and does not store excess duplicate bodies (Concepts; API Errors).
Why does a tight loop still return HTTP 202?
Spike saturation is a soft sample path, not a client error. Clients see success; operators see elevated event_count without proportional stored event rows (API Errors; Verify).
When does ingest return 403 ingest_cap_exceeded?
When the hourly project cap is hit. Default is 5000 events/hour/project. Cap is checked before enqueue. Wait for the window or raise the cap in project settings (API Errors).
Is there a Redis-backed global rate limiter?
No. The stack is two containers (Rust binary + PostgreSQL 16). Token bucket and hourly counter are in-process on the Rust binary (API Errors; Installation).
How is this different from SaaS Spike Protection or a hard 429?
Sentry SaaS Spike Protection is billing/quota-coupled; Sentry self-hosted docs state Spike Protection is not available there (third-party). Telebugs Ingest Protection (changelog 1.18.0) hard-rejects with 429 before writing (second-hand). Epure documented soft path is 202 + body drop + counter bump until the hourly 403 (first-party).
Can I publish measured N bodies kept of M from a flood script?
No public flood script or dated counter-vs-body table ships in the docs this week. Stick to documented curls and published defaults; omit retention ratios until a dated public table exists.