Telemetry ingest
Where the SDK ships the request log.
Generated from
packages/api/src/ingest/ingest.controller.ts by packages/docs/tools/generate-reference.mjs. Every example is a real exchange recorded against the control plane by packages/api/src/testing/reference-examples.spec.ts, with ids from that run. Credentials in the formats this product issues (kr_, krsk_, krses_, JWTs) and values under credential-named fields (password, *_secret, *_token, *_key, lookup_hash) are elided at record time; the recorded file and every page of this site are swept for credential shapes (kr_, krsk_, krses_, JWTs, URLs carrying a password, password fields) before either is accepted. Do not edit by hand: src/reference.spec.ts regenerates it and fails on a difference.POST /v1/ingest/batch -- the SDK's telemetry, and the only endpoint in the
product that scales with the customer's traffic rather than the vendor's.
Three consequences, all visible in this handler:
- It rate-limits itself, per workspace, before it does any work. Report section 9.5: "a rate-limiting company being taken down by unbounded ingest is an avoidable embarrassment". The charge happens first, and in its own committed transaction, so that a workspace over its limit costs one upsert rather than a thousand-row insert and so that a batch that goes on to fail still pays -- including one that fails validation, which is why the body is parsed inside the handler and not by a pipe.
- It writes no audit row. Every control-plane mutation does, inside its own transaction (migration 0003), and that write takes a per-workspace advisory lock. Taking it here would serialise the busiest endpoint we have behind the vendor's own dashboard writes -- the 22.7 s stall the week 2 review reproduced. Telemetry is traffic, not a control-plane change, and the request log is its own record.
- Everything the payload claims is re-checked. The workspace comes from
the authenticated principal; the project, tenant and key ids in the body
are strings an SDK sent, and
IngestService.assertOwnershipis where they stop being taken on trust.
POST /v1/ingest/batch
Answers 202 on success.
Authentication.
- A
krsk_secret key only. A person's session is refused on this plane.
Body. Read unparsed; validation happens inside the handler, after the charge or the signature check the handler's description explains.
Example.
curl https://keyring-api.belghalem.fr/v1/ingest/batch \
-X POST \
-H "Authorization: Bearer $KEYRING_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"object": "ingest_batch",
"protocol_version": 1,
"node_id": "api-eu-1",
"sdk": {
"name": "@keyring/express",
"version": "0.1.0"
},
"events": [
{
"ts": "2026-09-17T05:47:16.039Z",
"project_id": "01a0ade7-b8fb-7711-bb1a-7d1bd5962995",
"env": "test",
"tenant_id": "01a0ade7-b909-7ae2-9477-aa287f446aad",
"key_id": "01a0ade7-b916-78a6-801e-79f4ba8233fb",
"display_prefix": "kr_test_KVME4u",
"method": "GET",
"path": "/v1/orders",
"route": "/v1/orders",
"status": 200,
"duration_ms": 1.8,
"degraded": false,
"denial_reason": null,
"request_id": "req_01"
},
{
"ts": "2026-09-17T05:47:17.039Z",
"project_id": "01a0ade7-b8fb-7711-bb1a-7d1bd5962995",
"env": "test",
"tenant_id": "01a0ade7-b909-7ae2-9477-aa287f446aad",
"key_id": "01a0ade7-b916-78a6-801e-79f4ba8233fb",
"display_prefix": "kr_test_KVME4u",
"method": "GET",
"path": "/v1/orders",
"route": "/v1/orders",
"status": 200,
"duration_ms": 2.8,
"degraded": false,
"denial_reason": null,
"request_id": "req_02"
},
{
"ts": "2026-09-17T05:47:18.039Z",
"project_id": "01a0ade7-b8fb-7711-bb1a-7d1bd5962995",
"env": "live",
"tenant_id": "01a0ade7-b909-7ae2-9477-aa287f446aad",
"key_id": "01a0ade7-b935-77fe-bc3a-7dd4ef3c8dbe",
"display_prefix": "kr_live_mPXMEX",
"method": "GET",
"path": "/v1/orders",
"route": "/v1/orders",
"status": 200,
"duration_ms": 3.8,
"degraded": false,
"denial_reason": null,
"request_id": "req_03"
},
{
"ts": "2026-09-17T05:47:19.039Z",
"project_id": "01a0ade7-b8fb-7711-bb1a-7d1bd5962995",
"env": "test",
"tenant_id": null,
"key_id": null,
"display_prefix": null,
"method": "GET",
"path": "/v1/orders",
"route": "/v1/orders",
"status": 401,
"duration_ms": 4.8,
"degraded": false,
"denial_reason": "missing_key",
"request_id": "req_04"
}
]
}'HTTP/1.1 202 Accepted
Content-Type: application/json
{
"object": "ingest_receipt",
"accepted": 4,
"events_dropped_reported": 0
}Example: Over the per-minute budget (recorded with KEYRING_INGEST_MAX_EVENTS_PER_MINUTE=1000; the shipped default is 120000).
curl https://keyring-api.belghalem.fr/v1/ingest/batch \
-X POST \
-H "Authorization: Bearer $KEYRING_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"object": "ingest_batch",
"protocol_version": 1,
"node_id": "api-eu-1",
"sdk": {
"name": "@keyring/express",
"version": "0.1.0"
},
"events": [
{
"ts": "2026-09-17T05:47:20.039Z",
"project_id": "01a0ade7-b8fb-7711-bb1a-7d1bd5962995",
"env": "test",
"tenant_id": "01a0ade7-b909-7ae2-9477-aa287f446aad",
"key_id": "01a0ade7-b916-78a6-801e-79f4ba8233fb",
"display_prefix": "kr_test_KVME4u",
"method": "GET",
"path": "/v1/orders",
"route": "/v1/orders",
"status": 200,
"duration_ms": 1.8,
"degraded": false,
"denial_reason": null,
"request_id": "req_000100"
},
{
"ts": "2026-09-17T05:47:20.038Z",
"project_id": "01a0ade7-b8fb-7711-bb1a-7d1bd5962995",
"env": "test",
"tenant_id": "01a0ade7-b909-7ae2-9477-aa287f446aad",
"key_id": "01a0ade7-b916-78a6-801e-79f4ba8233fb",
"display_prefix": "kr_test_KVME4u",
"method": "GET",
"path": "/v1/orders",
"route": "/v1/orders",
"status": 200,
"duration_ms": 1.8,
"degraded": false,
"denial_reason": null,
"request_id": "req_000101"
},
"… 999 more"
]
}'HTTP/1.1 429 Too Many Requests
retry-after: 40
ratelimit: "ingest_events_per_minute";r=0;t=40
ratelimit-policy: "ingest_events_per_minute";q=1000;w=60
Content-Type: application/json
{
"error": {
"code": "rate_limited",
"message": "Too many requests."
}
}