> ## 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.

# Tailing request-log events — worked samples

> Connect to the live Server-Sent Events stream of your tenant's API request log, read the event frames, and resume from the last cursor after a disconnect.

## Tailing request-log events — worked samples

`GET /api/v1/tail` opens a Server-Sent Events (SSE) connection and streams every API request handled for your tenant — status code, latency, and request metadata — sub-second after each response completes. It is the data source behind the **Live tail** toggle on the developer Request Logs page.

The stream excludes health, readiness, and metrics probes, provider webhook ingress, and the tail endpoint itself, so what you see is your traffic only.

Because the response is an event stream — not a JSON document — this page documents the frames and fields below; copy the cURL sample as written and the stream prints to your terminal.

<Note>
  `GET /api/v1/tail`
</Note>

**Authentication:** API key (`X-API-Key`) or session JWT. The caller must hold the `owner`, `admin`, or `developer` role. If you cannot send headers — a browser `EventSource` — pass the credential as the `token` query parameter instead: a `dv_`-prefixed value is treated as an API key, any other value as a bearer JWT. From server code, always use the header; a credential in a URL ends up in access logs and proxy history.

**Query parameters**

| Parameter     | Type   | Notes                                                                                                                                                                                                                                                    |
| ------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`       | string | Header-free authentication for `EventSource` clients (see above). Omit when you can send `X-API-Key` or `Authorization`.                                                                                                                                 |
| `lastEventId` | string | Resume cursor. Set it to the `id` of the last frame you received and the server replays what you missed before continuing the live tail. Equivalent to the `Last-Event-ID` request header, which browser `EventSource` sends automatically on reconnect. |

### Open the stream with cURL

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

`-N` disables curl's output buffering — without it the frames arrive but stay invisible until the buffer fills. The browser equivalent passes the credential in the URL:

```bash cURL theme={null}
curl -N "https://api.orbit.devotel.io/api/v1/tail?token=dv_live_sk_your_key_here"
```

### Frame sequence

After connecting, the server sends, in order:

1. An `event: connected` handshake frame carrying the organization id.
2. A backfill of up to the most recent \~100 request rows, each an `event: log` frame.
3. New `event: log` rows live, as each request completes.

Comment lines (`: heartbeat`) arrive every 30 seconds to keep the connection open.

**The first frames**

<ResponseExample>
  ```text 200 theme={null}
  id: 0
  event: connected
  data: {"type":"connected","data":{"organizationId":"org_2aX7kQ","backfill_count":100}}

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

  : heartbeat
  ```
</ResponseExample>

The response has no `{ data, meta }` envelope — it is `Content-Type: text/event-stream`, so every frame above IS the response body.

**Log event fields — each `log` frame's `data:` line is one JSON object:**

| Field           | Type           | Notes                                                                      |
| --------------- | -------------- | -------------------------------------------------------------------------- |
| `type`          | string         | Always `log`.                                                              |
| `ts`            | integer        | Unix epoch milliseconds when the response completed.                       |
| `request_id`    | string         | Per-request id, also returned as the `X-Request-Id` header.                |
| `method`        | string         | HTTP method.                                                               |
| `path_pattern`  | string         | Normalized route with ids collapsed to `:id`, e.g. `/api/v1/messages/:id`. |
| `status_code`   | integer        | HTTP status code.                                                          |
| `duration_ms`   | integer        | Server-side handling time in milliseconds.                                 |
| `user_id`       | string \| null | Authenticated user, or `null` for API-key requests.                        |
| `api_key_id`    | string \| null | API key that authenticated the request, or `null`.                         |
| `error_code`    | string \| null | Machine-readable error code for `>= 400` responses, otherwise `null`.      |
| `error_message` | string \| null | Error message for `>= 400` responses (truncated), otherwise `null`.        |
| `user_agent`    | string \| null | Request `User-Agent` (truncated), or `null`.                               |

Adding fields is backwards-compatible — ignore keys you don't recognize.

### Subscribe from Node.js with EventSource

Named frames mean a generic `onmessage` handler never sees the rows — register a `log` listener:

<RequestExample>
  ```javascript Node.js theme={null}
  // npm install eventsource
  import { EventSource } from "eventsource";

  const source = new EventSource(
    "https://api.orbit.devotel.io/api/v1/tail",
    { headers: { "X-API-Key": process.env.ORBIT_API_KEY } },
  );

  let cursor; // persist the last id across restarts

  source.addEventListener("log", (event) => {
    cursor = event.id;
    forward(JSON.parse(event.data)); // your sink: log shipper, SIEM, on-call bot
  });

  source.onerror = () => {
    source.close();
    setTimeout(connect, backoff()); // EventSource re-sends Last-Event-ID = cursor
  };
  ```
</RequestExample>

### Resume after a disconnect

Every `log` frame carries an `id` (a stream cursor). To resume without losing rows, re-connect with `Last-Event-ID` (browser `EventSource` does this automatically) or pass the same value as `lastEventId`:

```bash cURL theme={null}
curl -N "https://api.orbit.devotel.io/api/v1/tail?token=dv_live_sk_your_key_here&lastEventId=1757726400123-0"
```

The server replays the rows newer than that cursor, then continues the live tail.

### Limits and errors

| Status | Meaning                                                                                                                     |
| ------ | --------------------------------------------------------------------------------------------------------------------------- |
| `403`  | Caller lacks the `owner`/`admin`/`developer` role, or (for browser clients) the request did not come from a trusted origin. |
| `429`  | The tenant has reached the maximum of 10 concurrent tail connections. Close an existing connection and retry.               |
| `503`  | The real-time backing store is temporarily unavailable. Retry with backoff.                                                 |

This is a live tail, not durable history. For a longer-horizon export of the same rows, route through your own sink (as in the Node.js sample) instead of polling the stream.
