ALTARED Telemetry API

Version v1 · versioned, additive changes only

Console

Quick Start

Register an application in the console, copy its API key once, then send your first event. Base URL for this deployment:

https://telemetry.example/api/public/v1
curl -X POST https://telemetry.example/api/public/v1/telemetry \
  -H "Authorization: Bearer $TELEMETRY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "event": "payment.completed",
    "severity": "info",
    "message": "Invoice 4021 paid",
    "tags": ["billing"],
    "data": { "invoice_id": "4021", "amount_cents": 12900 }
  }'

Authentication

Every request carries the application's own API key, either as Authorization: Bearer <key> or x-api-key: <key>. Keys are stored only as SHA-256 hashes, are shown exactly once, and carry least-privilege scopes:

  • telemetry:write — send telemetry
  • heartbeat:write — send heartbeats
  • telemetry:read / telemetry:search — read own telemetry
  • logs:read — read own log entries

There is no global master key. Revoking one application's credential never affects another.

Applications

Each application is fully independent: its own identity, credentials, environment and data. This service never connects to an application database, and an application never connects to the telemetry database.

GET https://telemetry.example/api/public/v1/application/status
Authorization: Bearer <key>

200 OK
{
  "application": { "id": "…", "name": "Billing Service", "environment": "production", "status": "healthy" },
  "last_telemetry_at": "2026-09-16T09:41:12.004Z",
  "last_heartbeat_at": "2026-09-16T09:44:02.551Z",
  "request_id": "req_…"
}

Telemetry

POST /api/public/v1/telemetry — requires telemetry:write.

{
  "event": "database.error",        // required, 1–120 chars
  "severity": "error",             // debug | info | warning | error | critical (default info)
  "message": "connection pool exhausted",   // optional, ≤1000 chars
  "occurred_at": "2026-09-16T09:40:00Z",   // optional client timestamp
  "tags": ["db", "pool"],                  // optional, ≤20 tags
  "idempotency_key": "…",                  // optional; header Idempotency-Key also accepted
  "data": { "pool": "primary", "waiting": 24 }   // optional JSON object
}

The server owns all security-sensitive metadata — application ID, environment, received timestamp, request ID, credential ID and processing status are derived from the authenticated credential, never from the request body.

202 Accepted
x-request-id: req_9f21…
x-telemetry-api-version: v1
x-ratelimit-limit: 600
x-ratelimit-remaining: 594
{ "ok": true, "accepted": 1, "duplicates": 0, "ids": ["…"], "request_id": "req_9f21…" }

Batching: send up to 100 events in one request by posting { "events": [ … ] } with the same event shape in each element. The response reports how many were accepted and how many were duplicates.

curl -X POST https://telemetry.example/api/public/v1/telemetry \
  -H "Authorization: Bearer $TELEMETRY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "events": [
        { "event": "service.started", "severity": "info" },
        { "event": "user.login", "severity": "info", "data": { "method": "google" } }
      ] }'

Verify a credential

GET /api/public/v1/verify is the only endpoint that proves a key works. It authenticates, reports the application identity, scopes and limits, and writes nothing to telemetry — so an application can self-check at startup without polluting data. GET /status requires no key and is never proof of a working credential.

curl https://telemetry.example/api/public/v1/verify \
  -H "Authorization: Bearer $TELEMETRY_API_KEY"

200 OK
{
  "verified": true,
  "application": { "id": "…", "name": "Billing Service", "environment": "production" },
  "scopes": ["telemetry:write", "heartbeat:write"],
  "limits": { "max_payload_bytes": 65536, "max_batch_events": 100, "requests_per_minute": 600 },
  "request_id": "req_…"
}

Reading collected telemetry (upstream hub)

GET /api/public/v1/events returns collected events in stable order with keyset pagination, so a consumer can resume exactly where it stopped and never skip or repeat a row. A credential with telemetry:read reads only its own application; telemetry:read:all (issued to the upstream analytics hub) reads every reporting application.

curl "https://telemetry.example/api/public/v1/events?limit=200&cursor=$CURSOR" \
  -H "Authorization: Bearer $TELEMETRY_READ_KEY"

200 OK
{
  "ok": true,
  "count": 200,
  "has_more": true,
  "next_cursor": "MjAyNi0…",            // persist this, send it back next poll
  "scope": "all_applications",
  "events": [
    {
      "id": "…",
      "application": { "id": "…", "name": "Billing Service", "slug": "billing-service" },
      "environment": "production",
      "event": "database.error",
      "severity": "error",
      "received_at": "2026-09-18T11:04:02.118Z",
      "occurred_at": "2026-09-18T11:04:01.902Z",
      "request_id": "req_…",
      "idempotency_key": null,
      "tags": [],
      "data": { }
    }
  ],
  "request_id": "req_…"
}

Optional filters: since (ISO timestamp), environment, severity, application_id (ignored unless the credential holds telemetry:read:all). limit is 1–500, default 100. Poll on an interval, persist next_cursor durably, and keep requesting while has_more is true.

GET /api/public/v1/fleet requires telemetry:read:all and returns every reporting application with its live status, last heartbeat, last event and seconds_since_last_signal — enough to render fleet health without reading events.

Heartbeats

POST /api/public/v1/heartbeat — requires heartbeat:write. Send on a fixed interval (30–60s is typical); a missing heartbeat is what marks an application offline.

curl -X POST https://telemetry.example/api/public/v1/heartbeat \
  -H "Authorization: Bearer $TELEMETRY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "ok", "version": "1.4.2", "metadata": { "host": "worker-3" } }'

Status

GET /api/public/v1/status is an unauthenticated liveness probe for the ingestion API. It never exposes application data.

{ "status": "ok", "service": "altared-telemetry", "version": "v1", "time": "2026-09-16T09:45:00.000Z" }

Errors

Errors are structured, human-readable and always carry a request ID. Stack traces are never returned.

401 invalid_credential        The API key is missing, unknown or not active.
403 permission_denied         The credential lacks the required scope.
403 application_disabled      The application is disabled in ALTARED Telemetry.
429 rate_limit_exceeded       Too many requests for this credential.
413 payload_too_large         Payload exceeds 65536 bytes.
400 invalid_payload           Payload failed schema validation (stored as a rejected event).
405 method_not_allowed        Wrong HTTP method for this endpoint.
503 service_unavailable       Telemetry database write failed — retry with backoff.

{ "error": { "code": "invalid_payload", "message": "…", "request_id": "req_…" } }

Malformed telemetry is never silently discarded: it is stored as a rejected event and logged.

Rate Limits

600 requests per minute per credential by default, plus a 65,536-byte payload limit. Exceeding either returns a structured error — treat both as retryable with backoff, never as a fatal application error.

Retries & Reliability

Telemetry observes your application; it must never control it. Client requirements:

  • Short timeout — 3 seconds or less.
  • Non-blocking — never await telemetry on a user-facing code path.
  • Retry with exponential backoff and jitter, capped at a few attempts.
  • Optional local buffering while this service is unreachable, with a bounded queue.
  • Swallow all telemetry errors: a telemetry outage must be invisible to your users.
async function sendWithBackoff(payload, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    try {
      const res = await postTelemetry(payload);   // 3s timeout inside
      if (res.ok || res.status < 500) return;      // 4xx is not retryable
    } catch { /* ignore */ }
    await new Promise((r) => setTimeout(r, 2 ** i * 250 + Math.random() * 100));
  }
  // give up silently — business functionality is unaffected
}

Idempotency

Send an Idempotency-Key header (or idempotency_key field) with every event. Retries with the same key are detected as duplicates and return 200 with { "duplicate": true } instead of storing the event twice.