Keyring

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.assertOwnership is 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.

Request
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"
      }
    ]
  }'
Response
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).

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

On this page