Ingest spec (v2)¶
The contract every Reqly SDK implements. Write SDKs for other languages against this document, not against the Python SDK's code.
Apps in other languages can also skip writing an SDK entirely and send OpenTelemetry
traces to POST /otlp/v1/traces instead. See OpenTelemetry.
Endpoint¶
POST /v1/ingest
Content-Type: application/json
X-Reqly-Key: <ingest key or a project key with the ingest scope>
| Response | Meaning | SDK should |
|---|---|---|
200 {"accepted": n, "rejected": m, "reasons": [...]} |
Batch processed. rejected events failed validation and were dropped individually; reasons (up to 5, only when something was rejected) says why. |
Treat as success. |
401 |
Missing or wrong ingest key. | Drop the batch, don't retry. |
422 |
Batch envelope invalid (e.g. service_name missing or too long, more than 1,000 events). |
Drop the batch, don't retry. |
408, 429, any 5xx |
Transient (rate limited, collector restarting, proxy error). | Retry with exponential backoff. |
Retries are safe: events are deduplicated on (timestamp, event_id), so resending a
batch that actually landed never double-counts.
Batch envelope¶
{
"service_name": "checkout-api",
"sdk_version": "0.2.0",
"release": "a1b2c3d",
"environment": "prod",
"events": [ ... ]
}
| Field | Type | Required | Limits | Notes |
|---|---|---|---|---|
service_name |
string | yes | 1–128 chars | One service per batch. |
sdk_version |
string | no | — | Informational. |
release |
string | no | ≤ 128 chars | v2. Default for events that don't set their own. Git SHA or version. |
environment |
string | no | ≤ 32 chars | v2. Default for events that don't set their own (prod, staging, …). |
backfill |
bool | no | default false |
Historical import: allows events older than 13 days. Each hour you send is rebuilt in the aggregates from exactly what you send, for every service, so only send complete hours. Needs the collector's REQLY_INGEST_KEY; a project key gets 403. |
events |
array | yes | 1–1,000 | Each event is validated independently. |
Unknown top-level fields are ignored, so newer SDKs can talk to older collectors.
Event¶
{
"event_id": "4f1c2b0e-8d7a-4c1e-9b3f-2a6d5e7c8b90",
"timestamp": "2026-10-09T14:03:22.418Z",
"method": "GET",
"route": "/orders/{id}",
"status_code": 200,
"duration_ms": 42.7,
"error": false,
"error_type": null,
"host": "web-7f9c",
"request_bytes": 0,
"response_bytes": 1832
}
| Field | Type | Required | Limits | Notes |
|---|---|---|---|---|
event_id |
UUID string | yes | — | Unique per request. Dedup key — generate once, reuse on retry. |
timestamp |
ISO-8601 with timezone | yes | ≤ 15 min in the future; ≥ now − 13 days unless the batch is a backfill |
When the request completed. Naive timestamps are rejected. Raw events are kept 14 days; an older straggler would otherwise overwrite days of aggregate history when it is materialized. |
method |
string | yes | 1–16 chars | HTTP method, upper case. |
route |
string | yes | 1–512 chars | The framework's route template (/orders/{id}), never the raw path (/orders/123). Send __unmatched__ when no route matched (404s, scanners). |
status_code |
int | yes | 100–599 | Response status sent to the client. |
duration_ms |
number | yes | 0–300,000 | Wall time from request start to response complete. |
error |
bool | yes | — | true for unhandled exceptions and any 5xx. |
error_type |
string | no | ≤ 255 chars | Exception class name, e.g. TimeoutError. |
host |
string | no | ≤ 255 chars | Hostname / pod name. Used for "errors concentrated on one host" hints. |
release |
string | no | ≤ 128 chars | v2. Overrides the batch-level value. |
environment |
string | no | ≤ 32 chars | v2. Overrides the batch-level value. |
consumer_id |
string | no | ≤ 128 chars | v2. Identifies the API client. Hash it in the SDK (e.g. HMAC-SHA256 with a per-app salt) — never send raw API keys or user ids. |
request_bytes |
int | no | 0–10 GiB | v2. Request body size. Omit or send null when unknown. |
response_bytes |
int | no | 0–10 GiB | v2. Response body size. Omit or send null when unknown. |
llm_model |
string | no | ≤ 255 chars | v2. Model used while serving this request, if any. |
llm_input_tokens |
int | no | 0–10,000,000 | v2. |
llm_output_tokens |
int | no | 0–10,000,000 | v2. |
Any other fields on an event are ignored.
SDK behaviour requirements¶
These are what make Reqly safe to run inside someone else's app. A new SDK should meet all of them.
- Fail open. No SDK error may propagate into the host application. On an internal failure, log once and disable instrumentation.
- Never block the request path. Record into an in-memory queue; ship from a background worker. HTTP calls use short connect/read timeouts.
- Bounded memory. Fixed-size queue; drop the oldest events when full and count the drops.
- Bounded cardinality. Route templates only (see
routeabove). - Batching. Up to 1,000 events per request; flush on an interval (default 5s) or when a batch fills.
- Retry policy. As in the response table above; exponential backoff with jitter, a small fixed number of attempts, then drop and count.
- Release detection.
releasefrom explicit config, thenREQLY_RELEASE, then common CI/hosting variables (GIT_COMMIT,GITHUB_SHA,CI_COMMIT_SHA,RENDER_GIT_COMMIT,VERCEL_GIT_COMMIT_SHA, …). - Fork safety where the runtime forks workers after import (e.g. Python's
gunicorn
--preload): restart the background worker in the child.
Changelog¶
- collector 0.12.1: a
backfillbatch needs the collector'sREQLY_INGEST_KEY, and rebuilds only the hours it contains (it used to rebuild every hour from its oldest event to now). - collector 0.8.1: events older than 13 days (unless the batch sets
backfill) or more than 15 minutes in the future are rejected, withreasonsin the response. OTLP spans follow the same rule (reported aspartialSuccess.rejectedSpans). - v2 (collector 0.3.0):
release,environment(batch and event level),consumer_id,request_bytes,response_bytes,llm_*. All optional; v1 payloads are unchanged and still accepted. - v1 (collector 0.1.0): initial contract.