# Live tail errors and reconnecting

> Handle refused log subscriptions before streaming, then resume accepted streams safely.

Canonical page: https://anectico.com/docs/investigate/live-tail-errors/


This contract applies to `GET /api/v1/stream/logs`. See
[Search logs, traces, and metrics](/docs/investigate/search-telemetry#logs) for filters and examples.

## Before the stream opens

A successful subscription returns HTTP `200` with `Content-Type: text/event-stream`.
A valid subscription opens even when no matching log exists yet; comment heartbeats do not
represent log records or advance your saved cursor.

A refused subscription returns an HTTP error with a JSON body **before any SSE heartbeat or
log frame**. Check the HTTP status before starting an SSE parser.

| HTTP status | JSON `error` | Action |
|---|---|---|
| `400` | `invalid_request` | Correct the request. A malformed, oversized, noncanonical or differently scoped resume cursor cannot be retried unchanged. |
| `401` | `unauthorized` | Authenticate again or replace the credential. |
| `403` | `forbidden` or `permission_denied` | Use the credential's project scope or obtain the required permission before trying again. |
| `429` | `rate_limited` | Retry with bounded backoff. |
| `503` | `unavailable` | Retry with bounded backoff; persistent failures may require a configuration change. |

For example, a refused log-tail cursor produces:

```http
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8

{"error":"invalid_request","message":"invalid log stream request"}
```

Admission is time-bounded. If the service cannot confirm the subscription, it fails rather
than opening an unvalidated stream. This opening bound does not limit how long an accepted
live tail can remain connected.

## Cursor and filter changes

Save a cursor only after processing its log frame. Reconnect with `Last-Event-ID`, or pass
`resume_cursor` in the query string. A nonempty `Last-Event-ID` takes precedence when both
are supplied. `since` applies only to a new subscription without a resume cursor.

A cursor belongs to its organization, effective project and filter set, including service,
level, text query, environment, customer IDs and the field filter. Keep these unchanged when
resuming. To change the filter set, intentionally start a new subscription without the old
cursor. Do not repeatedly retry a `400` with the same rejected cursor, and do not silently
reset your saved position after a refusal.

Treat cursors as opaque positions, not credentials. Authentication and authorization remain
required for every subscription.

## After the stream opens

Once the stream is accepted, a later failure cannot change the HTTP status. It is sent as
an SSE `error` event, for example:

```text
event: error
data: {"error":"invalid_request","terminal":true}
```

A `terminal: true` frame means the client must stop retrying the same subscription unchanged.
For a repairable failure, re-establish credentials when needed and reconnect with bounded
backoff from the last processed cursor. An accepted stream continues checking its credential;
revocation or scope loss can end it even while no new logs are arriving.

Error messages are intentionally bounded public classifications. Do not depend on private
validation details appearing in either JSON errors or SSE frames.
