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

# Tail the current workspace's live event stream over SSE or WebSocket

> The same live tail as /v1/stream, for the console and the CLI (`hookie tail`, `hookie listen`) rather than for a server-side integration: it authenticates with the browser session or a connected agent's bearer token instead of an ingest key, and the tenant comes from that credential rather than from the key. GET only — the route resolves before the admin CSRF guard, which only inspects mutating methods.

The stream is BEST-EFFORT by contract. Durable delivery is the outbound-delivery queue; a tail is for watching, and the absence of an event here never proves the event was not received. The resume buffer holds 1000 events and 5 minutes, and a resume outside that window succeeds but is preceded by a stream-gap control event.

An agent token needs hookie:read. Its 429 is the plan's concurrent-connection cap, counted across SSE responses and WebSockets together and across the console and the CLI alike — a console Observability tab left open counts as one.



## OpenAPI

````yaml /openapi.json get /admin/api/stream
openapi: 3.1.0
info:
  title: Hookie API
  version: 1.0.0
  summary: Capture, route and deliver webhooks.
  description: >-
    Hookie exposes three HTTP surfaces.


    **Ingest + streaming** is the public plane. A path-form endpoint URL
    authenticates by its own high-entropy slug — there is no key, token or
    cookie — and an `ik_live_…` ingest key authenticates the `/v1/*` routes.
    Whenever the URL names a dataset the payload is stored whole into it;
    project mapping rules are evaluated on exactly one shape, `POST
    /v1/ingest/{ingest_key}` with no dataset segment.


    **The customer portal API** is a separate, token-authed surface for your end
    customers, scoped to one portal and one customer.


    **The admin API** drives the console. It accepts either a browser session or
    an OAuth bearer token from a connected coding agent — the same routes, the
    same validation, the same audit trail. An agent's authority is its user's
    workspace role narrowed to the scopes granted in the console.


    This document is generated from the request handlers and adversarially
    verified against them.
  contact:
    name: Hookie
    url: https://hookie.ai
  license:
    name: Proprietary
    url: https://hookie.ai/legal/terms
servers:
  - url: https://app.hookie.ai
    description: Production
  - url: https://app.preview.hookie.ai
    description: Preview
security: []
tags:
  - name: Ingest
    description: Send events to Hookie.
  - name: Streaming
    description: Tail events in real time over SSE or WebSocket.
  - name: Portal
    description: Token-authed surface for your end customers.
  - name: Projects
    description: Projects and their settings.
  - name: Routing
    description: Rules, endpoints, datasets and records.
  - name: Delivery
    description: Destinations, deliveries and replay.
  - name: Workflows
    description: Multi-step workflows, triggers and AI agents.
  - name: Observability
    description: Search, correlation, stats and the audit log.
  - name: Workspace
    description: Members, connected agents, SSO and billing.
paths:
  /admin/api/stream:
    get:
      tags:
        - Streaming
      summary: Tail the current workspace's live event stream over SSE or WebSocket
      description: >-
        The same live tail as /v1/stream, for the console and the CLI (`hookie
        tail`, `hookie listen`) rather than for a server-side integration: it
        authenticates with the browser session or a connected agent's bearer
        token instead of an ingest key, and the tenant comes from that
        credential rather than from the key. GET only — the route resolves
        before the admin CSRF guard, which only inspects mutating methods.


        The stream is BEST-EFFORT by contract. Durable delivery is the
        outbound-delivery queue; a tail is for watching, and the absence of an
        event here never proves the event was not received. The resume buffer
        holds 1000 events and 5 minutes, and a resume outside that window
        succeeds but is preceded by a stream-gap control event.


        An agent token needs hookie:read. Its 429 is the plan's
        concurrent-connection cap, counted across SSE responses and WebSockets
        together and across the console and the CLI alike — a console
        Observability tab left open counts as one.
      operationId: adminStreamEvents
      parameters:
        - name: after_seq
          in: query
          required: false
          description: >-
            Resume cursor: replay buffered events with seq greater than this
            value. Ignored when a Last-Event-ID header is present. Non-numeric
            or negative values are treated as absent. The resume buffer holds at
            most 1000 events and drops anything older than 5 minutes; a request
            for evicted events still succeeds but is preceded by a stream-gap
            control event.
          schema:
            type: integer
            minimum: 0
        - name: Last-Event-ID
          in: header
          required: false
          description: >-
            Standard SSE resume header, sent automatically by EventSource on
            reconnect. Takes precedence over after_seq when present. Same
            semantics and same 1000-event / 5-minute buffer window.
          schema:
            type: integer
            minimum: 0
        - name: Upgrade
          in: header
          required: false
          description: >-
            Send exactly 'websocket' (compared case-sensitively) to get a 101
            WebSocket upgrade instead of an SSE stream. Any other value, or its
            absence, yields the SSE response.
          schema:
            type: string
            enum:
              - websocket
        - name: project_id
          in: query
          required: false
          description: >-
            Only deliver events belonging to this project. Applied by the stream
            Durable Object, not by the client, because the plan caps CONCURRENT
            CONNECTIONS (one, on Free) — a subscriber that attached to
            everything and filtered locally would spend its whole allowance to
            watch one project. STRICT: an event with no project (a workflow
            instance outside one, or anything buffered before events carried a
            project at all) does NOT match a project filter. The per-tenant
            `seq` cursor is unaffected, so a filtered stream skips numbers by
            design; a gap in seq is not a lost event. This is not a security
            boundary — the tenant gate is; an id from another tenant simply
            matches nothing.
          schema:
            type: string
        - name: dataset
          in: query
          required: false
          description: >-
            Only deliver events in this dataset. Combines with project_id — both
            have to match. Applied server-side for the same reason.
          schema:
            type: string
      responses:
        '101':
          description: >-
            WebSocket upgrade accepted (the request carried 'Upgrade:
            websocket'). Each live event arrives as one JSON text frame: {seq,
            id, dataset, received_at, data, project_id, webhook_id}. When a
            resume cursor was supplied and part of the requested window had been
            evicted, a
            {"type":"stream-gap","missed_from":<int>,"resume_from":<int>} frame
            is sent first, followed by the replayable buffered events. The
            socket is hibernatable and answers a 'ping' text frame with 'pong'.
            The live path is lossy by contract — durable delivery is the
            outbound-delivery queue, not this stream.
        '200':
          description: >-
            Server-sent event stream (no Upgrade header). Frames: ': connected'
            on open, ': keep-alive' every 25 seconds, one 'id: <seq>\ndata:
            <json>\n\n' frame per event where the JSON is {seq, id, dataset,
            received_at, data, project_id, webhook_id}, and — on a resume that
            missed evicted events — an un-ided 'event: stream-gap\ndata:
            {"missed_from":<int>,"resume_from":<int>}' control frame. A
            subscriber that stops draining is dropped once its backlog runs past
            the stream's high-water mark.
          headers:
            X-Accel-Buffering:
              description: >-
                Always 'no' — disables proxy/CDN buffering so events flush
                immediately.
              schema:
                type: string
                enum:
                  - 'no'
            Access-Control-Allow-Origin:
              description: >-
                Always '*'. Safe because the stream is key-authenticated and
                never cookie-authenticated; it also blocks any credentialed
                cross-origin EventSource.
              schema:
                type: string
                enum:
                  - '*'
            Cache-Control:
              description: Always 'no-store'.
              schema:
                type: string
          content:
            text/event-stream:
              schema:
                type: string
                description: >-
                  SSE frame stream; Content-Type is 'text/event-stream;
                  charset=utf-8'.
        '401':
          description: >-
            No session cookie and no usable bearer token, or the agent token
            failed verification against the issuer's JWKS.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: An agent token whose granted scopes do not include hookie:read.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: >-
            The tenant's plan cap on concurrent stream connections is reached
            (open SSE responses and WebSockets are counted together). This
            response comes from the stream Durable Object rather than the shared
            apiError helper, so it carries a Retry-After header and no
            CSP/security headers.
          headers:
            Retry-After:
              description: Always '30'.
              schema:
                type: string
                enum:
                  - '30'
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - type: object
                    required:
                      - limit
                    properties:
                      limit:
                        type: integer
                        description: the plan's concurrent-connection cap
                description: error is always 'stream connection limit reached'.
      security:
        - session: []
        - bearerAuth: []
components:
  schemas:
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: Human-readable reason.
        retry_after:
          type: integer
          description: Seconds to wait. Present on 429.
        upgrade_url:
          type: string
          format: uri
          description: Present only on a monthly-quota 429.
  securitySchemes:
    session:
      type: apiKey
      in: cookie
      name: hookie_session
      description: >-
        The console's sealed session cookie, set by WorkOS AuthKit. Mutating
        requests also require the `X-Requested-With` CSRF header.
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        An OAuth 2.1 access token from a connected agent, audienced at the
        `/mcp` resource URI. The agent acts as its user, with that user's role
        narrowed to the granted `hookie:*` scopes. No CSRF header is required —
        a bearer token is not ambient credentials.

````