Live tail errors and reconnecting
Handle refused log subscriptions before streaming, then resume accepted streams safely.
On this page
This contract applies to GET /api/v1/stream/logs. See
Search logs, traces, and metrics 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/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:
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.