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://api.keyringdev.com/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-dev/express",
      "version": "0.1.0"
    },
    "events": [
      {
        "ts": "2026-10-02T03:57:31.505Z",
        "project_id": "01a0fac2-a3b2-7769-a0aa-c32c811aa6ab",
        "env": "test",
        "tenant_id": "01a0fac2-a3d8-74da-a017-c3c8c3dbe8b3",
        "key_id": "01a0fac2-a3fb-71b0-851e-9a5c0f367d34",
        "display_prefix": "kr_test_D9a01n",
        "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-10-02T03:57:32.505Z",
        "project_id": "01a0fac2-a3b2-7769-a0aa-c32c811aa6ab",
        "env": "test",
        "tenant_id": "01a0fac2-a3d8-74da-a017-c3c8c3dbe8b3",
        "key_id": "01a0fac2-a3fb-71b0-851e-9a5c0f367d34",
        "display_prefix": "kr_test_D9a01n",
        "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-10-02T03:57:33.505Z",
        "project_id": "01a0fac2-a3b2-7769-a0aa-c32c811aa6ab",
        "env": "live",
        "tenant_id": "01a0fac2-a3d8-74da-a017-c3c8c3dbe8b3",
        "key_id": "01a0fac2-a43b-7c5e-803d-db48d46fd517",
        "display_prefix": "kr_live_t04EIa",
        "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-10-02T03:57:34.505Z",
        "project_id": "01a0fac2-a3b2-7769-a0aa-c32c811aa6ab",
        "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://api.keyringdev.com/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-dev/express",
      "version": "0.1.0"
    },
    "events": [
      {
        "ts": "2026-10-02T03:57:35.505Z",
        "project_id": "01a0fac2-a3b2-7769-a0aa-c32c811aa6ab",
        "env": "test",
        "tenant_id": "01a0fac2-a3d8-74da-a017-c3c8c3dbe8b3",
        "key_id": "01a0fac2-a3fb-71b0-851e-9a5c0f367d34",
        "display_prefix": "kr_test_D9a01n",
        "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-10-02T03:57:35.504Z",
        "project_id": "01a0fac2-a3b2-7769-a0aa-c32c811aa6ab",
        "env": "test",
        "tenant_id": "01a0fac2-a3d8-74da-a017-c3c8c3dbe8b3",
        "key_id": "01a0fac2-a3fb-71b0-851e-9a5c0f367d34",
        "display_prefix": "kr_test_D9a01n",
        "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: 25
ratelimit: "ingest_events_per_minute";r=0;t=25
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