Skip to content

Jobs

Every run-producing request returns a job envelope with an id of the form job-<uuid>. The /v1/jobs surface is where you read that run to completion — by polling or by subscribing to a live event stream — and where you return the outputs of any client-side tool the run paused on.

The envelope type is JobStatus, and it is identical across every kind of run (completion, and the gated companion / team surfaces).

status flow:   pending ──► running ──► complete
                                   └──► failed
                                   └──► requires_action  (paused on a tool call)

GET /v1/jobs/{id}

Poll a job's current state.

Parameter In Type Description
id path string The full job-<uuid> id from the enqueue response.
curl https://api.humx.ai/v1/jobs/job-1a2b3c4d-... \
  -H "Authorization: ApiKey $COMPANIONS_API_KEY"
import os, time, requests

headers = {"Authorization": f"ApiKey {os.environ['COMPANIONS_API_KEY']}"}
job_id = "job-1a2b3c4d-..."

while True:
    job = requests.get(f"https://api.humx.ai/v1/jobs/{job_id}", headers=headers).json()
    if job["status"] in ("complete", "failed", "requires_action"):
        break
    time.sleep(1.0)

print(job["status"], job.get("content"))
const headers = { Authorization: `ApiKey ${process.env.COMPANIONS_API_KEY}` };
const jobId = "job-1a2b3c4d-...";

let job;
do {
  await new Promise((r) => setTimeout(r, 1000));
  const res = await fetch(`https://api.humx.ai/v1/jobs/${jobId}`, { headers });
  job = await res.json();
} while (!["complete", "failed", "requires_action"].includes(job.status));

console.log(job.status, job.content);
const headers = { Authorization: `ApiKey ${process.env.COMPANIONS_API_KEY!}` };
const jobId = "job-1a2b3c4d-...";

async function poll(): Promise<JobStatus> {
  for (;;) {
    const res = await fetch(`https://api.humx.ai/v1/jobs/${jobId}`, { headers });
    const job = (await res.json()) as JobStatus;
    if (["complete", "failed", "requires_action"].includes(job.status)) return job;
    await new Promise((r) => setTimeout(r, 1000));
  }
}

Response 200 — a JobStatus. Returns 404 if the id is malformed, unknown, or belongs to another account.


GET /v1/jobs/{id}/stream

Subscribe to the run as a Server-Sent Events stream. Each event's data: is a JobStatus frame; the stream closes cleanly after the first terminal (complete or failed) frame.

Parameter In Type Description
id path string The job-<uuid> id.
Last-Event-ID header string Optional. Resume from a cursor after a reconnect.
curl -N https://api.humx.ai/v1/jobs/job-1a2b3c4d-.../stream \
  -H "Authorization: ApiKey $COMPANIONS_API_KEY"
data: {"id":"job-...","kind":"completion","status":"running",
       "details":{"stage":"node_firing","node":"Ada"}}

data: {"id":"job-...","kind":"completion","status":"complete",
       "details":{"stage":"done"},
       "content":{"shape":"answer","response":"..."}}

The JobStatus envelope

Field Type Description
id string job-<uuid>.
kind string completion (or companion / team / forensics).
mode string The completion mode; null for non-completion runs.
status string pending, running, complete, failed, or requires_action.
details object Stage / decision / error / pause detail — and, on success, the run's balance_after and cost (see below).
content object The typed result — present only on a completed completion run.
stages array Per-node progress rows (observability). Each row carries a cost — that node's spend, summed across every LLM call it made.

details on success

On a completed run, details carries stage: "done" plus two billing fields so you can read what the job cost without a separate call:

Field Type Description
stage string "done" on a terminal complete.
balance_after number Your remaining credit, in USD, at poll time — the live balance reflecting every charge committed so far (including concurrent runs), not a frozen per-run snapshot. Saves you a GET /v1/billing/balance round-trip.
cost number What this run cost, in USD — its net charge (all charge rows minus any refund). Counts every LLM call the run made, including retried and non-winning attempts, so it can exceed the sum of stages[].cost. Omitted on un-metered runs.
{ "stage": "done", "balance_after": 12.4817, "cost": 0.0006 }

Both fields appear only on the GET /v1/jobs/{id} poll of a terminal complete run — they are absent from intermediate frames and from the SSE stream (streamed frames stay lean).

details on a failed run carries the error instead:

{ "stage": "error", "error_type": "ZDRUnavailable", "message": "..." }

Result shapes

On a completed completion, content.shape tells you which mode ran and which fields to read.

shape Key fields
answer companion, response
answer_crumbs companion, response, crumbs[]
parallel responses[] (each: name, response)
parallel_with_main main_companion, main_response, participants[]
panel synthesis, aggregator_companion, stage1[], stage2[]
discussion summary, summarizer_companion, turns[]
{
  "shape": "parallel",
  "responses": [
    { "name": "Architect", "response": "..." },
    { "name": "SRE",       "response": "..." }
  ]
}

Tool-call pauses

If a run uses client tools and the model calls one, the job does not complete — it pauses at status: "requires_action" with content: null. The details list the pending calls:

{
  "status": "requires_action",
  "details": {
    "stage": "requires_action",
    "node": "main",
    "pending_tool_calls": [
      {
        "call": {
          "id": "call_abc123",
          "type": "function",
          "function": { "name": "read_user_file", "arguments": "{\"path\":\"main.py\"}" }
        },
        "tool": {
          "name": "read_user_file",
          "description": "Read a file from the user's local workspace.",
          "parameters": { "type": "object", "properties": { "path": { "type": "string" } } }
        }
      }
    ]
  }
}

Each entry pairs what to run (call) with how to run it (tool, the echoed declaration). Run each tool locally, then return the outputs.

Return tool outputs

POST /v1/jobs/{id}/tool_outputs

Deliver the results of a paused run's tool calls. Supply exactly one output per pending call, keyed by tool_call_id. The run resumes and continues toward a terminal state.

Field Type Required Description
tool_outputs array One { "tool_call_id", "output" } per pending call.
Parameter In Type Description
Idempotency-Key header string Optional. Retry-safe key; a repeat with the same key and body replays the original result.
curl https://api.humx.ai/v1/jobs/job-1a2b3c4d-.../tool_outputs \
  -H "Authorization: ApiKey $COMPANIONS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "tool_outputs": [
          { "tool_call_id": "call_abc123", "output": "print(\"hello\")" }
        ]
      }'
import os, requests

requests.post(
    f"https://api.humx.ai/v1/jobs/{job_id}/tool_outputs",
    headers={"Authorization": f"ApiKey {os.environ['COMPANIONS_API_KEY']}"},
    json={"tool_outputs": [
        {"tool_call_id": "call_abc123", "output": 'print("hello")'}
    ]},
).raise_for_status()
await fetch(`https://api.humx.ai/v1/jobs/${jobId}/tool_outputs`, {
  method: "POST",
  headers: {
    Authorization: `ApiKey ${process.env.COMPANIONS_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    tool_outputs: [{ tool_call_id: "call_abc123", output: 'print("hello")' }],
  }),
});
await fetch(`https://api.humx.ai/v1/jobs/${jobId}/tool_outputs`, {
  method: "POST",
  headers: {
    Authorization: `ApiKey ${process.env.COMPANIONS_API_KEY!}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    tool_outputs: [{ tool_call_id: "call_abc123", output: 'print("hello")' }],
  }),
});

Bounds (each a 422): 1–32 outputs; each ≤ 256 KiB; ≤ 1 MiB total; no embedded NUL bytes.

A pause expires

A paused run holds its buffered state for about an hour. Submit your tool outputs before then, or the run transitions to failed and you must restart the completion.

Response 200 — the resumed JobStatus.