ZenodeZenode
Endpoints

Async AI jobs

Asynchronous AI job tracking

AI endpoints — discovery and deep dive — take seconds to minutes, so they run as jobs. Each creates a job and returns immediately; you then follow that one job to completion.

ai:run
  • POST an AI endpoint → 202Accepted with a Job (status: queued).
  • Poll GET /v1/jobs/{job_id} — the same endpoint serves both progress and result.
  • Or stream GET /v1/jobs/{job_id}/stream for live progress.
  • List your jobs with GET /v1/jobs.

The Job object

JSON
1{
2 "job_id": "job_8f3a2b...",
3 "type": "discover",
4 "status": "running",
5 "progress": {
6 "message": "Reading datasheet — Electrical Characteristics",
7 "fraction": 0.4,
8 "updated_at": "2026-06-01T17:05:12Z"
9 },
10 "created_at": "2026-06-01T17:04:00Z",
11 "completed_at": null,
12 "stream_url": "https://api.zenode.ai/v1/jobs/job_8f3a2b.../stream",
13 "usage": {
14 "input_tokens": 18234,
15 "output_tokens": 2451,
16 "total_tokens": 20685
17 },
18 "result": null,
19 "error": null,
20 "request_id": "req_2c19..."
21}

See Job for the full field reference. The result shape depends on type — a DiscoverResult for discover jobs or a DeepDiveResult for deep_dive jobs. While running, status and the coarse progress object track the work; usage reports tokens consumed.

Poll a job

GEThttps://api.zenode.ai/v1/jobs/{job_id}

Returns the full Jobstatus and progress while running, the typed result inline once completed. Poll until status is terminal. 404Not Found once the job ages out (see retention below).

Stream a job

GEThttps://api.zenode.ai/v1/jobs/{job_id}/stream

Server-Sent Events for live progress. Each frame is a named event: with a JSON data: line, in standard SSE wire format. A small, stable set of event types is emitted (internal agent chatter is summarized, not passed through):

ParameterTypeRequiredDescription
status
{ status }OptionalLifecycle transition — queued, running, then a terminal status
progress
{ message, fraction }OptionalCoarse progress — message is a step label, fraction an optional 0–1 estimate
pick
DiscoverPickOptionalDiscovery jobs only — one ranked pick, emitted as it's decided
result
DiscoverResult | DeepDiveResultOptionalTerminal success — the typed Job.result for this job's type
error
{ type, message }OptionalTerminal failure

A discovery stream looks like:

TEXT
1event: status
2data: {"status":"running"}
3
4event: progress
5data: {"message":"Reading datasheet — Electrical Characteristics","fraction":0.4}
6
7event: pick
8data: {"rank":1,"part":{ /* PartDetail */ },"fit_rationale":"Lowest sleep current with a hardware step counter."}
9
10event: result
11data: {"summary":"All three are active, sub-3.6 V 6-axis IMUs…","results":[ /* DiscoverPick[] */ ]}

The result event carries exactly the typed Job.result for this job's type — a DiscoverResult for discover jobs or a DeepDiveResult for deep_dive jobs. A deep_dive stream omits pick events.

Reconnecting

The stream survives disconnects and client refreshes. Each event has an id; on reconnect, send the last id you saw in a Last-Event-ID header and the server resumes from the next event without gaps:

BASH
1curl -N https://api.zenode.ai/v1/jobs/job_8f3a2b.../stream \
2-H "Authorization: Bearer $ZENODE_API_KEY" \
3-H "Last-Event-ID: 42"

If the job already reached a terminal state, reconnecting replays through the final result (or error) event so you never miss the outcome, up to the retention window.

Webhooks are coming soon. Polling and streaming are the supported ways to follow a job today; a job-completion webhook is on the roadmap.

List your jobs

GEThttps://api.zenode.ai/v1/jobs

Lists your own jobs, newest first.

ParameterTypeRequiredDescription
status
stringOptionalFilter by status; accepts a single value or active (= queued + running)
type
stringOptionalFilter by discover | deep_dive
limit
intOptionalPage size, default 20, max 100
starting_after
stringOptionalPagination cursor — pass a prior response's next_cursor

Returns a cursor-paginated page of JobSummary rows — a Job without the heavy result:

JSON
1{
2"jobs": [ /* JobSummary[] */ ],
3"next_cursor": "job_6a1f...",
4"request_id": "req_77de..."
5}

next_cursor is null on the last page; otherwise pass it back as starting_after. Fetch GET /v1/jobs/{job_id} for a row's full payload. This is the only cursor-paginated collection in the API.

Cancel a job

POSThttps://api.zenode.ai/v1/jobs/{job_id}/cancel

Best-effort cancel of an in-flight job. Idempotent:

JSON
1{
2 "job_id": "job_8f3a2b...",
3 "cancelled": true
4}

Retention

Jobs and their results are retained for 72 hours after creation, fetchable on GET /v1/jobs/{job_id} and replayable on the stream. After that they age out to 404Not Found.

Next