Skip to main content

Quickstart

What you’ll build

You will create a run with POST /v1/runs, subscribe to progress with Server-Sent Events (SSE) using an authenticated connection, detect when the run reaches a terminal state, and confirm the final status with GET /v1/runs/{id}. For the full run state machine, event catalog, and awaiting_input patterns, see the linked guides below.

Prerequisites

  • Node.js 20+ and npm
  • An API key in the form key_id:secret — see Authentication for creating keys with POST /v1/api-keys, or use a key provisioned during account setup
  • SSE with auth headers: install the helper library (native EventSource cannot send Authorization):
npm install @microsoft/fetch-event-source

Step 1: Configure credentials

Read the API base URL and key from the environment. Use http://localhost:3000 (or your dev URL) for local development.
export API_BASE="https://api.example.com"
export API_KEY="<key_id>:<secret>"
In TypeScript:
const API_BASE = process.env.API_BASE ?? "https://api.example.com";
const API_KEY = process.env.API_KEY!; // format: key_id:secret
Send the key as Bearer <key_id>:<secret> (the raw key_id:secret after Bearer ).

Step 2: Create a run

POST /v1/runs accepts JSON and requires an idempotency-key header for safe retries.
import { randomUUID } from "node:crypto";

type CreateRunResponse = {
  id: string;
  status: string;
  request_id?: string;
  replayed?: boolean;
};

async function createRun(): Promise<CreateRunResponse> {
  const res = await fetch(`${API_BASE}/v1/runs`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
      "idempotency-key": randomUUID(),
    },
    body: JSON.stringify({
      input: { user_query: "Summarize the key findings" },
      metadata: {},
    }),
  });

  const body = (await res.json()) as Record<string, unknown>;

  if (!res.ok) {
    const err = body as {
      error?: string;
      reason_code?: string;
      request_id?: string;
    };
    throw new Error(
      `Create run failed (${res.status}): ${err.reason_code ?? "unknown"}${err.error ?? JSON.stringify(body)} (request_id=${err.request_id ?? "n/a"})`,
    );
  }

  const run = body as CreateRunResponse;
  // On first create, run.status is typically "queued"
  return run;
}
Response shape (happy path): { id, status: "queued", …, request_id }. Extract run.id for subsequent calls.

Step 3: Stream events via SSE

Subscribe to GET /v1/runs/{id}/events/stream. The server emits SSE events named run_event; each message’s data is JSON matching the public envelope (no database id on the wire):
type PublicRunEvent = {
  seq: number;
  type: string;
  timestamp: string;
  payload: { redacted: boolean; value: Record<string, unknown> };
};
Use fetchEventSource from @microsoft/fetch-event-source so you can set Authorization.
import { fetchEventSource } from "@microsoft/fetch-event-source";

async function streamRunEvents(
  runId: string,
  onEvent: (event: PublicRunEvent) => void,
): Promise<void> {
  const url = `${API_BASE}/v1/runs/${runId}/events/stream`;
  const ac = new AbortController();

  try {
    await fetchEventSource(url, {
      signal: ac.signal,
      headers: { Authorization: `Bearer ${API_KEY}` },
      onmessage(msg) {
        if (msg.event !== "run_event" || !msg.data) return;
        const event = JSON.parse(msg.data) as PublicRunEvent;
        onEvent(event);

        const t = event.type;
        const terminal =
          t === "run.worker.succeeded" ||
          t === "run.worker.failed" ||
          t === "run.cancelled" ||
          t === "run.limit_exceeded";
        if (terminal) ac.abort();
      },
    });
  } catch (e) {
    if (ac.signal.aborted) return;
    throw e;
  }
}
Streaming assistant text: on step.progress, when payload.value.kind === "content_delta", write payload.value.content_delta to stdout (or your UI buffer). step.done outcomes: payload.value.outcome is one of succeeded, retry_step, or fail_run (not a generic failed label). Human-in-the-loop: if you receive run.awaiting_input, resume the run with POST /v1/runs/{id}/signal and an action of approve, reject, or submit_input — see Run lifecycle and Authentication.

Step 4: Check the result

After the stream ends for a terminal event, fetch the run record:
async function getRun(runId: string): Promise<{ id: string; status: string }> {
  const res = await fetch(`${API_BASE}/v1/runs/${runId}`, {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });
  if (!res.ok) {
    const body = await res.json();
    throw new Error(`GET run failed: ${JSON.stringify(body)}`);
  }
  return res.json();
}

// Example
const run = await getRun(runId);
console.log("Final status:", run.status);

Step 5: Handle errors

SituationWhat to do
401 with AUTH_KEY_INVALID or AUTH_MISSING_HEADERConfirm Authorization: Bearer <key_id>:<secret> and that the key is active
404 on GET /v1/runs/{id}Wrong run id, or run belongs to another customer scope
409 on POST /v1/runs with duplicate idempotency-keySame key + same body returns the existing run; response may include replayed: true — treat as success for idempotent clients
run.worker.failed eventInspect payload.value.reason_code for the failure cause
Error responses use a JSON envelope with fields such as error, reason_code, and request_id.

Next steps