Appearance
Limits & Errors
Beta
Telemetry is in beta: included with paid plans at no extra cost during the beta, with limits subject to change.
Plans
Telemetry is on paid plans. The Free plan excludes telemetry: no Stream creation, and data sent to a Free account's Streams gets 403 plan_required.
| Plan | Streams | Events per month | Retention | Requests per minute | Data per minute |
|---|---|---|---|---|---|
| Hobby | 2 | 1,000,000 | 7 days | 3,000 | 64 MB |
| Pro | 5 | 10,000,000 | 14 days | 12,000 | 256 MB |
| Team | 20 | 50,000,000 | 30 days | 30,000 | 1 GB |
| Founders | 5 | 10,000,000 | 14 days | 12,000 | 256 MB |
| Enterprise | Custom | Custom | Up to 90 days | Custom | Custom |
Events, requests and data are counted across every Stream in every namespace on the account, not per Stream.
| Plan | Telemetry monitors | Derived metrics | Dashboards |
|---|---|---|---|
| Hobby | 5 | 5 | 3 |
| Pro | 25 | 25 | 10 |
| Team | 200 | 200 | 50 |
| Founders | 25 | 25 | 10 |
| Enterprise | 1,000 | 1,000 | 500 |
Monitors and derived metrics run at most once a minute.
Monthly events
Every stored record counts as one event, except the time-on-page pings the Analytics script sends on its own. Each Stream's Overview shows the account's total for the month.
- At 80% and 100% the account owner gets an email, and the console shows a banner.
- At 110% ingest pauses until the 1st of next month (UTC): requests get
429 quota_exceeded. Upgrading lifts the pause within a minute, and stored data is never affected.
Rate limits
Budgets are per rolling minute and apply across all our servers, so bursts are fine as long as the minute's total stays under the budget.
- Account budget: the plan's requests and data per minute (table above). Data is the request body as sent, so compression stretches the budget further.
- Key limits (optional): cap a single ingest key below the account budget, for example a key shipped in browser code. Key limits live under Keys & Setup → Limits.
- Fair share: when ingest falls behind and our buffer passes half full, the Streams holding the most buffered data slow first (
429 buffer_fair_share), so one flood can't hold up everyone else.
A 429 always carries Retry-After, and names the limit that was hit in the X-Latency-Limit header and the body's limit field:
limit | Meaning |
|---|---|
account_requests_per_minute | Over the plan's requests per minute |
account_bytes_per_minute | Over the plan's data per minute |
key_requests_per_minute | Over this key's own request limit |
key_bytes_per_minute | Over this key's own data limit |
buffer_fair_share | Ingest is catching up and this Stream holds more than its share |
monthly_events | The account is at 110% of its monthly events (quota_exceeded) |
Throttled requests show up in the Stream's Overview, under usage and Ingest Health.
Requests
| Limit | Value |
|---|---|
| Request body | 4 MB (compressed size, when compressed) |
| Decompressed body | 32 MB |
| Records per request | 100,000 |
Batch whenever possible: one request with 1,000 records costs far less than 1,000 requests.
Records
| Limit | Value | Over the limit |
|---|---|---|
| Log message or event body | 1,048,576 characters | The excess is dropped |
| Attributes per record | 256 | Extra attributes are dropped |
| Attribute name | 256 characters | The excess is dropped |
| Attribute value | 16 KB | The excess is dropped |
| Nesting depth | 8 levels | Deeper objects are stored as JSON text |
Nothing is dropped silently. The record is still stored, and carries a latency.dropped attribute saying what was dropped (for example body:2000000>1048576 attrs:+56), so the search latency.dropped:* finds every affected record. Each Stream's Overview counts records with dropped data and shows why, with an example.
Timestamps
- More than 24 hours in the future: replaced with the receive time.
- Older than the stream's retention: rejected, since the record would be deleted straight away.
Responses
| Status | Meaning | What to do |
|---|---|---|
202 (HTTP API) or 200 (OpenTelemetry) | Stored | Nothing |
400 stream_type_mismatch | OTLP sent with an Events Stream's key | Use a key from an OpenTelemetry Stream (types) |
401 | Key missing, wrong, disabled or revoked, or its Stream is deleted | Check the key in Manage Streams |
403 plan_required | The account's plan doesn't include telemetry | Upgrade the plan |
403 ingest_blocked | We blocked this key, Stream or namespace; the body's reason says why | Contact feedback@latency.app |
413 | Body over 4 MB | Send smaller batches, or compress |
415 | Unsupported Content-Type or Content-Encoding | See the HTTP API or OpenTelemetry setup |
429 rate_limited | Over a rate limit | Retry after the Retry-After header |
429 quota_exceeded | The account is at 110% of its monthly events | Upgrade, or wait for next month |
429 ingest_backlog_full | Our buffer is full while ingest catches up | Retry after the Retry-After header |
503 | Ingest briefly unavailable | Retry after the Retry-After header |
429 and 503 are safe to retry. OpenTelemetry SDKs and the Collector retry automatically. For the HTTP API, add an Idempotency-Key so a retry never stores twice. A 403 won't succeed on retry, so SDKs drop the data instead.
Unreadable records
A 202 means the request was stored, before parsing. If a record turns out to be unreadable (say, a metric with no name, or a timestamp older than the Stream's retention), that record is skipped and the rest of the request is kept. A body that can't be read at all (invalid JSON, say) is set aside whole.
Either way the problem stays visible: the Stream's Overview lists every reason from the last 7 days with a count and an example, and counts rejected records in usage.