Skip to content
TestLookupTestLookup
Search Ctrl K

Search TestLookup

Enter a term to search the local static index.

    Stream live results

    While your tests run, reporters can stream results to TestLookup, and the web app shows them live. This page lists each live route, the credential it needs and the IDs it returns. Most teams stream through a reporter library (SDK) instead of calling these routes directly. See SDK, CLI, and MCP.

    A live ID can be a readable name, called a slug, instead of a UUID. Use the test_run_id that TestLookup returns for run APIs and run links. Do not build a run address from the live slug. Each route returns the run’s UUID in a named field:

    RouteRun UUIDOther IDs
    POST /api/v1/stream/sessionssession_id. The route creates a new UUID, so session_id, run_id and the stored run’s ID are the same value.session_token, which you send as X-Session-Token on POST /api/v1/stream/events/batch; project_id; expires_in
    POST /api/v1/stream/ingestsession_idrun_id repeats the ID you sent, which may be a slug; accepted; created_session
    GET /api/v1/stream/sessions/{session_id}, GET /api/v1/stream/activetest_run_idrun_id is the display ID, which may be a slug

    The live WebSocket, /ws/live/{project_id}, expects the client’s first frame to contain an auth type and a sign-in token (JWT). If that sign-in is malformed, TestLookup can close the connection with code 4400. If it times out or fails, TestLookup can close it with code 4401. Project membership still applies.

    The first frame can add "protocol": 2 to receive batched live_batch frames. TestLookup’s connected reply then states the protocol and the frame_interval_ms (200). Every client receives at most one data frame per interval, which is five a second.

    For administrators: each user’s live session on a project writes one ws_session row to the audit log, once it has been connected for 5 seconds. A reconnect within 60 seconds of the last disconnect joins the same row. Connecting and disconnecting write no other audit rows. The testlookup_ws_connections_total metric counts connections by outcome: audited, merged, short or audit_failed.

    SSE (server-sent events) is a one-way live connection from the server. The live-session SSE route, /api/v1/stream/sse/{project_id}, takes the sign-in token in a token query parameter. It can also accept a Last-Event-ID header. It is separate from the SSE stream of an agent invocation, which uses tickets.

    An agent invocation is an AI agent run, started with POST /api/v1/agents/{agent_id}/invoke. It streams its progress at GET /api/v1/agents/invocations/{invocation_id}/events.

    To read that stream, first create a ticket with POST /api/v1/agents/invocations/{invocation_id}/events/ticket, using a credential that can access the invocation. A ticket is short-lived and works only once, so every reconnect needs a new ticket.

    When you reconnect, use only the replay ID format of the stream you are reading. For the live WebSocket, the optional last_event_id uses the Redis stream ID format. Do not reuse one route’s token or position in another route.

    A reconcile_required frame with a dropped count means the client fell behind and some frames were dropped. Read the current state again instead of assuming that every event arrived.

    Live totals can appear before every test row is stored. Each of these has its own state: the run’s completion, the closing of sessions left open, follow-up work, analysis and review.

    Closing a session starts storing its details straight away. If that fails, the outbox relay does it later. A scheduled job runs every two minutes and closes sessions that have been idle for five minutes.

    Check the exact route in the streaming routes reference and the API reference before you write a client.

    Despite its path, /ws/events/{run_id} is an ordinary POST route for sending events, and it needs a credential. With the default LIVE_EVENTS_REQUIRE_PROJECT_KEY=true, it accepts only a project API key and refuses the shared webhook secret. If the key is limited to certain actions, those must include stream:write.

    This route takes one event per call. Each call counts against INGEST_EVENT_RATE_LIMIT_PER_MINUTE, which allows 20,000 events per project per minute by default. The batch routes share a separate limit of 200 batches a minute, so use the batch routes when you send many results.

    RouteWhat it doesCredential
    POST /api/v1/stream/sessionsSends: opens a sessionSign-in token or any API key; QA engineer or higher
    GET /api/v1/stream/sessions/{session_id}Reads the session’s stateSign-in token or any API key
    DELETE /api/v1/stream/sessions/{session_id}Sends: closes and finishes the sessionSign-in token or any API key; QA engineer or higher
    POST /api/v1/stream/events/batchSends: a batch of eventsThe X-Session-Token from opening the session
    POST /api/v1/stream/ingestSends: a batch with a key only; the first call opens the sessionProject API key with stream:write, or with no limits
    POST /api/v1/stream/http-exchangesSends: the HTTP requests and responses one live test capturedSign-in token or any API key; QA engineer or higher
    GET /api/v1/stream/activeReads active and recent sessionsSign-in token or any API key
    GET /api/v1/stream/sse/{project_id}Receives: live-session SSESign-in token in the token query parameter
    /ws/live/{project_id} (WebSocket)Receives: live framesSign-in token in the first frame
    POST /ws/events/{run_id}Sends: one eventProject API key, by default

    The WebSocket and SSE routes are not in the OpenAPI document.

    1. A reporter library opens a session with the API, or sends events with its credential.
    2. The API holds the events in a Redis stream.
    3. One event processor reads the events from Redis and publishes them again, in order, for every API process.
    4. Each API process sends the updates to its own connected users, over WebSocket or SSE, for the projects they can access.
    5. The processor saves the held test details in the database.
    6. When the reporter says the run is complete, the API schedules the work that finishes it.
    7. The processor finishes the run in the database and records its follow-up jobs.

    TestLookup processes each event once and then sends it out to every API process. That way, each API process can deliver the same events, in the same order, to its own connected users. Every user must sign in and sees only the projects they can access. As described above, build run links from the returned test_run_id, not from a live slug.

    Live totals can appear before every test row is stored. The remaining details and follow-up work are filled in when a session closes, or when TestLookup closes a session that was left open. If a reporter never says its run is complete, scheduled jobs close the session, so the web app does not show it as live forever. How often those jobs run is a setting, not a delivery promise. The code is in the stream service, the recovery service and the fan-out code.

    Once a run is stored, TestLookup writes its follow-up jobs to a list saved with the run, called the downstream outbox. The jobs include storing live details, starting the AI pipeline, comparing suites, notifications and run-completed webhooks. Each entry names the run, the project, the job, the version of its input, its queue and the data it needs. A recovery job retries unfinished work, and each job is safe to run twice. Starting, running and finishing a job each have their own retry limit.

    A deleted run stays deleted, because late writes cannot recreate it. Cancelling an AI pipeline does not cancel your CI job or delete its results. A finished test run can still have a pending or failed AI pipeline, and the other way round. So the web app and the API show the two separately.

    When you report a problem, give each ID separately: the project ID, run ID, task ID, pipeline ID and stage or invocation ID. Each of these answers a different question: the logs, the project activity, the import counters, the live delay and queue metrics, the task status, and the outbox and pipeline records. A run produces stored test results, history and totals first, then analysis and report records. The reports guide explains the rules for exported reports.

    The generated reference for background tasks, queues and schedules lists every background task and when the scheduler runs it.

    The first WebSocket frame looks like {"type":"auth","token":"<JWT>"}. The optional last_event_id uses the Redis stream ID format, two numbers joined by a dash (digits-digits). A client that does not add "protocol": 2 keeps the older frame types. The other rules for the WebSocket and SSE routes are in the sections above.

    TestLookup also has health routes:

    • /health/live checks that the process is running.
    • /health/ready checks the services it cannot work without.
    • /health/version reports which build is running.
    • /health/details reports on each service it depends on. It can answer 200 with the status degraded, which means some of those services have problems.

    The /metrics route exists only when metrics are configured, and it is left out of the exported OpenAPI document. A healthy answer alone does not prove that the workers process jobs, that the AI model is installed correctly or that the database updates match the code.

    See the feature page on collecting results for the live reporters TestLookup provides.