Limits
Morse has three limits you can actually hit, and each one returns a different 429 so you can
tell them apart without guessing:
| What ran out | Error code | Is it a billing event? |
|---|---|---|
| Monthly billable-span allowance | event_limit_exceeded | Yes — upgrade or wait for the period to reset |
| Infra-span fair use | INFRA_SPAN_FAIRUSE_EXCEEDED | No — a storage ceiling, never billed |
| Hourly / daily share of the allowance | INGEST_HOURLY_QUOTA / INGEST_DAILY_QUOTA | No — resumes when the UTC hour or day rolls over |
| Ingest rate | RATE_LIMIT_INGEST / SPAN_BURST_LIMIT | No — retry shortly |
| Fair share of ingest capacity | INGEST_ORG_RATE_LIMIT / INGEST_CONCURRENCY_LIMIT | No — retry shortly |
Every one of them sets Retry-After to when retrying will actually work: a second for a rate
limit, the next hour or day boundary for a window quota, the end of your billing period for the
monthly allowance.
What counts against your plan
Only spans your own code authored are billable. Spans of type db and http — the ones our
auto-instrumentation creates for you — are ingested, stored, correlated, and shown in the trace
exactly like any other span, and are never counted against your monthly allowance.
This matters more than it sounds. On a typical auto-instrumented workload we measured about
11.5 infrastructure spans for every billable span — roughly 90% of all spans are db spans
our SDK wrote, not calls you made. Metered naively, that would mean two bad things:
- You could not forecast your bill from anything you control. An N+1 query in your code would arrive as a billing incident rather than a performance one.
- Correlating infrastructure into your traces — the thing Morse is for — would be the thing that multiplied your bill.
So we took it off the meter. Correlate as much infrastructure as you want; we don’t bill you for spans you didn’t write.
The no-silent-raise commitment
This is a promise, not a current-behavior note:
The set of non-billable span types may only ever grow.
When we add a new auto-instrumentation source — cache, middleware, queue, anything — it will not increase the billable volume of any existing customer. A Morse SDK upgrade can lower what you’re metered for. It cannot raise it.
Without this, “we don’t bill for auto-instrumented spans” would be worth very little: we could ship a new instrumentation source next month, your span count would jump, and nothing in your code would have changed. The commitment closes that door and is the reason you can size a plan against your own code rather than against our release schedule.
The current non-billable set is db and http. If it ever changes, it changes by getting
longer.
Infra span fair use
Exempting infrastructure spans from the meter does not exempt them from storage, so they are bounded rather than billed. The ceiling is 10× your plan’s billable-span allowance per month, and hitting it returns:
{
"error": "INFRA_SPAN_FAIRUSE_EXCEEDED",
"message": "Fair-use ceiling of … auto-instrumented (db/http) spans reached …",
"current_count": 750000,
"limit_count": 750000,
"plan_id": "free",
"docs_url": "https://docs.morsehq.dev/limits#infra-span-fair-use"
}This is not a billing event and never becomes one. There is deliberately no upgrade prompt or usage-breakdown pressure in that response — it is a storage guard, and it is scoped to spans you are not paying for.
Because the measured real-world ratio is about 11.5:1 against a billable allowance already sized
above expected usage, a 10× ceiling leaves substantial headroom over normal auto-instrumented
volume. In practice you reach it only through runaway instrumentation — most often a polling loop
or health check instrumented per request, writing thousands of near-identical db spans an hour.
If you hit it:
- Look at the traces with the highest
db/httpspan counts — a single repeating caller is the usual culprit. - Stop instrumenting the hot path that is generating them, or reduce its call rate.
- If your workload genuinely needs the volume, a higher plan raises the ceiling with the allowance, since the ceiling is derived from it.
The counter resets with your billing period.
Ingest rate limits
Two independent gates protect ingestion, one on requests and one on spans:
- Requests per second, per API key. Sustained abuse far above your plan’s limit trips an organization-level circuit breaker that returns
503 CIRCUIT_BREAKER_OPENwith aRetry-Afterheader. Follow that header before retrying; contact support if ingestion remains suspended. - Spans per second, per organization. Request limits alone cannot control maximum-size batches, so batch size is budgeted too. It is checked before your batch is added — a single large batch on an idle connection is never rejected by this burst budget.
Both return Retry-After: 1. The Morse SDKs already retry with backoff, so if you are using one
you generally do not need to handle these yourself.
Fair-share limits
Ingest capacity is shared, so no single account may use more than its share of it — however cheap each request is. Two per-organization limits enforce that, across all of your API keys and every ingest endpoint (traces, OTLP traces, logs and metrics):
- Requests per second, per organization (
INGEST_ORG_RATE_LIMIT). The per-key limit above still applies; this one exists so that adding keys or SDK processes cannot multiply it. - Requests in progress at once, per organization (
INGEST_CONCURRENCY_LIMIT). Each request holds a slot while we process it. A normal SDK sends one batch every couple of seconds per process and never gets near this; a tight loop of tiny requests does.
Both scale with your plan and return 429 with Retry-After: 1. Fewer, larger batches are the
fix in almost every case — the SDK’s default batching already does this.
If an organization’s accepted traffic still occupies far more than its share for several minutes
running, its ingestion is suspended with 503 CIRCUIT_BREAKER_OPEN (honor Retry-After) and our support team is notified straight away. Throttling of any kind notifies us too, so we may reach out before you
notice.
Hourly and daily quotas
Your monthly allowance is also spread across time: one UTC hour and one UTC day can each use only
a fixed share of it. The shares are set per plan and are generous against steady traffic — a day
can use several times an average day’s worth — but they mean a runaway loop cannot spend your
whole month in an afternoon, and one busy account cannot crowd out everyone else’s ingest.
Billable spans and auto-instrumented (db/http) spans are counted separately, each against its
own monthly ceiling. Logs and metrics are not counted here.
When a window is spent, span ingest returns:
{
"error": {
"code": "INGEST_HOURLY_QUOTA",
"message": "This organization has used its hourly allowance of spans …",
"details": {
"window": "hour",
"span_class": "billable",
"limit": 1500,
"used": 1500,
"plan": "free",
"retry_after_seconds": 2800,
"resets_at": 1700003600
},
"docs_url": "https://docs.morsehq.dev/limits#hourly-and-daily-quotas"
}
}Retry-After is the number of seconds until that UTC hour (or day) ends. The rejection happens
before your payload is read, so it is cheap for both sides. Hitting it regularly means your
traffic is burstier than your plan is sized for: sample, batch less often, or upgrade.
When ingestion is backlogged
If our queue of accepted batches is backed up, POST /api/v1/traces/batch answers
503 INGEST_BACKPRESSURE with a Retry-After instead of accepting your batch. Nothing from that
request was stored, and the Morse SDKs retry it for you. We refuse rather than accept a batch we
might not be able to keep: a 202 from Morse always means the batch is queued.
Related
- Usage — this period’s consumption against your allowance.
- Billing — plans, allowances, and invoices.
- Data model — what a span is and how span types are assigned.