> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Live tail streaming model: what the live request-log stream guarantees

> What the tenant-scoped live request-log tail actually streams, its connect-replay-live frame sequence, entry payload, buffering and drop policy under backpressure, per-tenant isolation and connection limits, and when the tail answers a question versus when request logs, the webhook debug log, or the stats surface does.

# Live tail streaming model

The live tail is a real-time stream of your tenant's API request log, delivered over Server-Sent Events (SSE) from `GET /api/v1/logs/tail`. Every authenticated request to the Orbit API completes, and its row — method, path, status, latency, request id — reaches every connected tail within sub-second. It is the data source behind the **Live tail** toggle on the dashboard's request-logs page, and you can open it directly from your own tooling.

This page explains the stream's semantics and guarantees. Endpoint schemas, per-parameter auth, and SDK consumption snippets live in the [Tail API reference](/api-reference/log-tail); the console walkthrough is in the [Request Logs console guide](/guides/api-request-logs-console). What the tail deliberately is **not** — a durable log store — and how it differs from the persisted query surface is covered here and in the [Request Logs model](/concepts/request-logs-model).

## 1. What the tail is — and is not

The tail is a **tenant-scoped, live-only debugging surface**:

* **Scoped to one tenant per connection.** Every row that crosses the stream was authenticated against your organization; the connection itself sees exactly that one tenant's traffic. Two tenants' tails never share rows, a connection cap, or a Redis channel.
* **Sub-second from response to frame.** A request's row is published from the response-completed hook and lands on open connections while the next request is still in flight — the bar this surface ships against is sub-second, not the tens of seconds a provider console log typically lags.
* **A live view, not a system of record.** The stream carries only recent rows plus whatever sends while you are connected. The durable, queryable record of your API traffic is the [Request Logs surface](/concepts/request-logs-model) — a persisted log corpus with cursor pagination over roughly a year of retained history. Reaching the tail without a reconnect cursor gives you a snapshot for watching now, not an archive.

What flows on the stream and what does not:

* **All authenticated `/api/v1/*` requests** — REST and SDK calls made by your users and API keys.
* **Unauthenticated requests are never published.** A row exists only if the request resolved to your tenant, so probing, forged, and expired-credential traffic never reaches you.
* **Excluded by design:** health, readiness, and metrics probes, the tail endpoint itself, and inbound provider **webhook** ingress (`/api/v1/webhooks/…`, `/api/v1/integrations/webhooks/…`). Those receiver-side requests are operator/provider noise on this surface — provider signature failures and replay would read as "customer traffic errors" and dilute the signals the inbound webhook surfaces own. Webhook live-debugging lives in the [inbound webhook debug log](/concepts/inbound-webhook-debug-log).

## 2. Stream shape: connect → optional replay → live

The transport is plain SSE over `GET /api/v1/logs/tail` — one long-lived HTTP response in `text/event-stream`. After connecting, the server sends, in order:

1. **A `connected` handshake frame** carrying your organization id and the backfill size the stream is about to paint.
2. **A backfill of the most recent \~100 request rows** in chronological order, so a fresh console session has immediate context.
3. **Live rows** — one named `log` frame per completed request, oldest-to-newest, for the rest of the connection.

Keepalive is built in: a `: heartbeat` comment frame every 30 seconds, plus TCP keepalive at the same cadence, so an idle connection stays open across proxies and a vanished browser tab is reclaimed from your connection cap promptly.

**Reconnecting and replay.** Every `log` frame carries an SSE `id` that is a durable stream cursor. If the connection drops, send that cursor back — `Last-Event-ID` (browser `EventSource` does this automatically), or the `lastEventId` query parameter from a client that cannot set headers — and the server replays the rows newer than your cursor before resuming live. Two boundaries to internalize:

* **The replay buffer is bounded.** It holds only recent stream history, capped at roughly the same \~100-row window the connect backfill paints. A cursor that has fallen out of the buffer behaves like a fresh connect: you get the backfill and the live stream, and anything older is gone from this surface.
* **Missed history is not lost — it just isn't here.** The durable answer to "what happened while I was disconnected" is the [Request Logs surface](/concepts/request-logs-model), whose persisted corpus the tail deliberately does not try to replicate.

**Frame order within one row burst is arrival order.** The server publishes each row after that request's response completes; within one connection you read it in the order the rows were emitted. Across concurrently connected tails of the same tenant, trips through different API replicas mean two clients can disagree by a row or two momentarily — every client converges on the same stream, but live-not-durable means you never reconcile them.

## 3. Entry payload model

One `log` frame's `data:` line is one JSON object — a compact, per-request row:

| Field                          | Notes                                                                                                                                                                                                                                                                 |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`                         | Always `log`.                                                                                                                                                                                                                                                         |
| `ts`                           | Epoch milliseconds when the response completed.                                                                                                                                                                                                                       |
| `request_id`                   | Per-request id — the same value returned to your caller as the `X-Request-Id` response header, so a support report like "request `req_…` 404ed" pastes straight into a live watch.                                                                                    |
| `method`                       | HTTP method.                                                                                                                                                                                                                                                          |
| `path_pattern`                 | Normalized route — UUIDs, long opaque ids, and numeric path segments collapse to `:id`, so `/api/v1/messages/msg_abc123` reads as `/api/v1/messages/:id`. This is deliberate redaction: the streamed path never carries a message id, contact id, or conversation id. |
| `status_code`                  | HTTP status.                                                                                                                                                                                                                                                          |
| `duration_ms`                  | Server-side handling time, end to end within the API.                                                                                                                                                                                                                 |
| `user_id`                      | The authenticated user for session-authenticated calls, or `null` for API-key calls.                                                                                                                                                                                  |
| `api_key_id`                   | The API key that authenticated an API-key call, or `null`.                                                                                                                                                                                                            |
| `error_code` / `error_message` | Machine-readable error code and a truncated message on `>= 400` rows, `null` otherwise.                                                                                                                                                                               |
| `user_agent`                   | The request's `User-Agent` (truncated), or `null`.                                                                                                                                                                                                                    |

**What is redacted by construction.** Only the fields above exist on the stream — request bodies, response bodies, query strings, and headers never cross it, and path normalization removes embedded ids before the row is even published. The surface carries enough to debug routing, status, and latency without shipping payload content into a live broadcast; inspecting actual payload bytes belongs to the bounded, per-row [payload view in the Request Logs console](/guides/api-request-logs-console).

Adding fields is backwards-compatible — ignore keys you do not recognize.

**Dashboard Live tail toggle vs the raw endpoint.** The **Live tail** toggle on the dashboard request-logs page is a client of the same endpoint: it opens an EventSource against `GET /api/v1/logs/tail` with your session token and paints rows into the table as they arrive. The raw endpoint is for your own tooling — server-side log forwarders feeding a SIEM, an on-call bot, or a CLI watch while you integrate. Same stream, same role gate (`owner`/`admin`/`developer` on the tenant), same drops and caps; the toggle is a browser convenience, not a second surface.

## 4. Delivery semantics: buffering, drops, isolation, and limits

The stream is a best-effort live view on top of a bounded in-memory transport. That produces four explicit guarantees rather than one vague one:

* **Per-tenant isolation.** Each tenant's traffic publishes to its own channel; a single tail connection subscribes to exactly one channel after authenticating to that tenant. No cross-tenant row is ever emitted, and no other tenant's bursts, drops, or reconnects affect your stream.
* **Connection cap.** A tenant may hold at most **10 concurrent** tail connections, counted cluster-wide. An 11th attempt gets `429` with `TOO_MANY_CONNECTIONS`; closed tabs and dead peers release their slot promptly (heartbeat cadence), and even a slot leaked wholesale self-heals within an hour. Keep your own consumers to one or two long-lived connections.
* **Slow-consumer drop policy.** Each connection's outbound buffer carries a soft cap (1 MB) and a hard cap (5 MB). Over the soft cap, the server **drops the frame**, writes a `: slow_consumer` advisory comment frame, and continues — your client stays connected but missed a row, and the cursor you resume from still finds the row in the replay buffer if it is recent. Over the hard cap, the connection is force-closed (the server logs it; you reconnect with your last cursor). Sustained slow consumption therefore degrades you to an incomplete-but-connected stream before it ever risks the API process.
* **Failure posture of the pipeline.** Row publishing is fire-and-forget from the response hook — a transient outage of the real-time backing store never slows your API responses down. If the backing store is unavailable when you connect, the connection attempt answers `503` rather than hanging; once connected, a replay or backfill failure degrades to live-rows-only, not an error on the stream. Treat `503` and `429` from a server consumer as back-off-and-retry conditions.

Rate limiting is the standard authenticated-read budget on the connect call itself; the **data plane** of an open stream is not rate-limited per frame.

## 5. Which observability surface answers which question

The [operator observability map](/concepts/operator-observability-map) frames the full surface set; this table is the request-level slice of it:

| If you need…                                                                 | Use                                                                                                       | Why this surface                                                                                                                                          |
| ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "Show me my API traffic **right now**, while I integrate"                    | **[Live tail](/concepts/live-tail-streaming-model)** — dashboard toggle or `GET /api/v1/logs/tail`        | Sub-second, tenant-scoped, role-gated, no persistence. The only surface meant to be watched live.                                                         |
| "Find the request that failed yesterday / page over history for forensics"   | **[Request Logs](/concepts/request-logs-model)** — persisted log corpus                                   | Durable, queryable across \~1 year of retention, cursor-paginated, filterable by method/status/path/key/request-id. The system of record the tail is not. |
| "Why did the inbound webhook from a provider fail signature / never process" | **[Inbound webhook debug log](/concepts/inbound-webhook-debug-log)**                                      | Pre-signature, PII-masked inbound record, separate tenant/platform visibility split — the tail deliberately excludes this ingress.                        |
| "How is my traffic trending / count aggregates for a dashboard or wallboard" | **[Stats surface](/concepts/observability-stats-surface)** — `/api/v1/stats/…` and the analytics pipeline | Pre-aggregated, replica-backed trend reads. Cheap for trend questions; never the answer to "did this exact call succeed."                                 |

A decision rule the table compresses: the moment you need history, you are done with the tail.

## 6. Worked example: a session with reconnect

Open the stream against the tenant whose traffic you own, then keep your last cursor:

```bash theme={null}
curl -N "https://api.orbit.devotel.io/api/v1/logs/tail" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Accept: text/event-stream" \
  -H "Origin: https://orbit.devotel.io"
```

First frames on the wire (comments and named events verbatim):

```
: heartbeat

id: 0
event: connected
data: {"type":"connected","data":{"organizationId":"org_9f2b…","backfill_count":100}}

id: 1812345678901-0
event: log
data: {"type":"log","ts":1758123456789,"request_id":"req_7f3c2a","method":"POST","path_pattern":"/api/v1/messages","status_code":202,"duration_ms":41,"user_id":null,"api_key_id":"key_ab12","error_code":null,"error_message":null,"user_agent":"MyApp/1.4"}
```

Save each `id:` you receive. On a drop, reconnect with it and the server fills the gap from the replay buffer before going live:

```bash theme={null}
curl -N "https://api.orbit.devotel.io/api/v1/logs/tail" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Accept: text/event-stream" \
  -H "Origin: https://orbit.devotel.io" \
  -H "Last-Event-ID: 1812345678901-0"
```

From a browser (`EventSource` cannot set headers), pass the token as the `token` query parameter and the cursor as `lastEventId` — the dashboard's Live tail toggle does exactly this. **Server-side consumers never put the credential in the URL** (it lands in proxy and access logs): use the `X-API-Key` header and set headers through your SSE/http client, per the [Tail API reference](/api-reference/log-tail).

## Related reading

* [Tail API reference](/api-reference/log-tail) — endpoint contract, frame schema, SDK and curl consumption, limits and errors.
* [Request Logs model](/concepts/request-logs-model) — the persisted request-log surface the tail complements.
* [Request Logs console guide](/guides/api-request-logs-console) — the dashboard page, including the Live tail toggle.
* [Inbound webhook debug log](/concepts/inbound-webhook-debug-log) — the inbound-side surface the tail deliberately excludes.
* [Observability stats surface](/concepts/observability-stats-surface) and [operator observability map](/concepts/operator-observability-map) — where aggregates and the wider surface set fit.
