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. |
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. |
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:
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. |
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.