Skip to main content

Live request-log tail

GET /api/v1/logs/tail opens a Server-Sent Events (SSE) connection that streams every API request handled for your tenant, sub-second after each response completes. It is the data source behind the Live tail toggle on the developer Request Logs page, and you can consume it directly from your own tooling. The stream excludes health, readiness, and metrics probes, as well as the tail endpoint itself. Base path: /api/v1/logs/tail Authentication: API key (X-API-Key) or session JWT. The caller must hold the owner, admin, or developer role.

Using the SDKs

Prefer the typed client, but this page’s endpoint has no helper yet — the generic request() keeps auth/retries and the { data, meta } envelope identical:
Raw curl in the body of this page works identically. Full SDK index at SDK quickstart.

Connect

Browsers use EventSource, which cannot set request headers, so pass your credential as the token query parameter instead. A dv_-prefixed value is promoted to X-API-Key; any other value is treated as a bearer JWT. When you call from a server you can send the X-API-Key or Authorization header directly and omit token.

Frames

After connecting, the server sends, in order:
  1. An event: connected handshake frame with data: {"type":"connected", ...}.
  2. A backfill of up to the most recent ~100 request rows.
  3. New rows live, as each request completes.
Comment lines (: heartbeat) are sent every 30 seconds to keep the connection open. Each request row arrives as a named log frame — listen with addEventListener("log", …), not onmessage, because EventSource only routes named frames to a matching listener. Each log frame’s data: line is one JSON object:
Adding fields is backwards-compatible — ignore keys you don’t recognize, and drop any row missing a required field.

Resume after a disconnect

Every frame carries an id (a stream cursor). To resume without losing rows, reconnect with the Last-Event-ID request header set to the last id you received — browser EventSource does this automatically. From a client that can’t set the header, pass the same value as the lastEventId query parameter:
On resume the server replays the rows newer than that cursor before continuing the live tail.

Consume from Node.js

Server-side consumers — log forwarders, ETL jobs, on-call bots — authenticate with the X-API-Key header (or an Authorization bearer header). Never send token as a query parameter from server code: a URL ends up in access logs and proxy history, and a credential in it is exposed. If your server library parses named SSE events, use the named event (addEventListener("log", …)), not a generic message handler — the rows arrive as event: log frames, so an onmessage-style handler never sees them.

Consume from Python

Same rules as Node: header auth, named log events, a persisted cursor. sseclient dispatches each frame’s event: field to callbacks you register per name — register log, not message. With requests you iterate lines and track the fields yourself:
Keep the saved cursor in durable storage (a file, Redis, a database row) and re-send it as Last-Event-ID on reconnect — the server replays the rows you missed.

Limits and errors

On 429 or 503 from a server-side consumer, back off before retrying instead of reconnecting in a tight loop:
This is a live tail, not durable history. It carries only recent rows and the live stream — use it for observability, not as a system of record.