Jobs
A job is the unit of work that you submit. Computalot expands it into tasks: one per input, chunk, or candidate. Computalot runs the tasks on matching capacity and returns structured results.
Submit with POST /api/v1/jobs: your project name, your command, and a payload.
Choosing a job type
| I want to… | Use |
|---|---|
| Run one script with JSON input/output | structured_runner |
| Run code across a list of inputs | structured_runner + fan_out.by |
| Evaluate many tiny inputs per worker task | structured_runner + fan_out.by / fan_out.items + batch_size |
| Evaluate CMA/evolutionary candidates | structured_runner + fan_out.items |
| Train a model on a GPU | structured_runner + profile: "gpu" |
| Search a parameter grid | sweep |
| Run simulations and reduce results | map_reduce |
| Compare named strategies | benchmark |
| Submit many jobs at once | POST /api/v1/jobs/batch |
When in doubt, use structured_runner.
Submitting a job
curl -sS https://computalot.com/api/v1/jobs \
-X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "structured_runner",
"runner_command": ["python3", "evaluate.py"],
"payload": {"model": "gpt-4", "dataset": "test_v3"},
"project": "my-project",
"timeout_s": 600
}'The response carries the job id, the status, and summary.billing_estimate, the authoritative cost quote for this run (see Pricing). timeout_s caps the runtime of each task. Computalot never charges for queue time.
The runner protocol
Your script talks to Computalot through files and exit codes. The task imports nothing:
- Computalot writes the JSON payload of the task to a temp file and points
$COMPUTALOT_TASK_PAYLOADat it - Your script reads it, does the work, and writes JSON to
$COMPUTALOT_TASK_RESULT - Exit
0marks the task completed. Any non-zero exit marks it failed
The exit code is the final word. Result JSON cannot override it. result_quality and result_warnings are reserved and return null and []. The API accepts and ignores legacy object-shaped result_schema metadata. Non-object values return 422.
To report progress, print a line to stdout:
print(f"COMPUTALOT_PROGRESS:{json.dumps({'step': 42, 'loss': 0.05})}")Stdout/stderr streams into live_feedback.output_tail for the task and into the SSE job stream while the task runs. If your runner wraps a child process, run it unbuffered or flush explicitly. Logs then appear as they happen.
Fan-out
Three shapes, for three kinds of parallelism:
{"fan_out": {"by": "models"}}— split one array field inpayloadinto one task per item{"fan_out": {"items": [{...}, {...}]}}— provide the exact payload object for each task{"fan_out": {"chunks": 20, "range_field": "total_seeds", "total": 10000}}— split a numeric range into chunk tasks
Select exactly one shape. A request that mixes by, items, or chunks + total returns 422. When each item is small, add batch_size (or batch_per_task). One dispatched task then processes several items locally, and batched tasks receive payload._batch metadata. For parameter grids, use the sweep job type instead of fan-out.
Watching a job
| You want | Use |
|---|---|
| Poll status | GET /api/v1/jobs/:id every 2–5s |
| Live stream, one job | GET /api/v1/jobs/:id/stream — SSE, includes running-task output tails |
| Live stream, many jobs | GET /api/v1/jobs/watch?ids=... — one SSE stream. Frames carry client_ref, tags, meta, variant, summary fields, and persistence flags |
| Live stream, whole project | GET /api/v1/projects/:name/stream |
| Per-task detail | GET /api/v1/jobs/:id/tasks — live_feedback, latest_progress, checkpoint/resume state |
| Final results | GET /api/v1/results/:job_id — per-task results, aggregates, completeness, artifact IDs |
| Stdout/stderr | GET /api/v1/jobs/:id/output — during retries, it keeps the output of the last failed attempt until the new attempt writes its own |
| Files your job produced | GET /api/v1/artifacts, then GET /api/v1/artifacts/:id |
| Cancel | PUT /api/v1/jobs/:id/cancel |
| Edit requirements | PATCH /api/v1/jobs/:id/requirements |
List and filter jobs
GET /api/v1/jobs lists your jobs, newest first. GET /api/v1/results lists your terminal jobs in result form. Both endpoints accept the same filters:
| Filter | Effect |
|---|---|
status=completed,failed | One status or a comma-separated list. terminal expands to the four terminal statuses. |
since=2026-08-06T00:00:00Z | Jobs with submitted_at at or after the cutoff. A date such as 2026-08-06 reads as midnight UTC. |
tag=experiment_42 | Jobs whose tags array contains the exact value. |
ids=job_a,job_b | A specific set of job IDs in one call. |
project=, type=, client_ref= | Exact-match filters. |
limit=, offset= | Pagination. limit is 1–200 (default 50 on /jobs, default 20 on /results). |
If a client must re-sync after a disconnect, pass the last successful sync time as since and page with limit and offset. Unknown status values and malformed since values return 422 with a recommended_action.
Lifecycle
planning → queued → running → completed | partial | failed | cancelled
completed: every task succeededpartial: at least one task succeeded and at least one failed or was cancelledfailed: no task succeeded and the work ended in failurecancelled: the job was cancelled before all work completed
Options
- Retries —
"max_retries": 0..10re-runs failed tasks after the initial attempt. The submit-time hold covers the whole retry budget. Infrastructure failures (lost worker, OOM kill) requeue without this budget. If only your code can see that the machine is broken, write"failure_class": "infra"in the result JSON and exit non-zero. Computalot then requeues the task without a charge to the retry budget, at most 3 times for each task. Each attempt still bills for its runtime. When other capacity is available, a retry runs on a different machine than the one that failed the task. - Dependencies —
"depends_on": ["job_id", ...]accepts up to 50 of your job IDs. Dispatch waits until every dependency iscompletedorpartial. Afailedorcancelleddependency cancels the blocked job. - Artifact handoff between stages — read the concrete artifact ID from the upstream
GET /api/v1/results/:job_id. Then pass it inpayload._artifacts.download. The API validates ownership and records the reference before it creates work or a billing hold. - Tags —
"tags": ["experiment_42"](max 20), then filter withGET /api/v1/jobs?tag=experiment_42. - Client label —
"client_ref": "batch_7"(max 255 bytes) groups related jobs. Search finished work withGET /api/v1/results?client_ref=batch_7. - Batch submit —
POST /api/v1/jobs/batchtakes up to 200 jobs in one request. Successful entries keepindex,payload,meta, andvariant. - Shared coordination values — store small project-scoped JSON with
PUT /api/v1/projects/:name/kv/:key. Read it before submission, and pass the resolved value inpayload.payload._shared.resolveis not supported and returns422. - Webhooks — not available yet: non-empty
callback_urlvalues return422. Use SSE, watch, or polling.
Sizing requirements
requirements are minimums, not machine picks. Computalot can place your task on anything at least that large. Size storage_gb for more than your dataset. The runtime image, writable caches, temp files, checkpoints, and sandbox overhead all share that disk. Heavy ML runtimes often need tens of GB free before any weights download.
If one project serves both light CPU jobs and heavy GPU training, split them into separate runtimes. Do not ship one oversized environment everywhere. Smaller runtimes place faster.
If a task is single-thread bound, set requirements.min_single_core_score to keep it off slow CPU cores. The score is relative single-core speed with baseline 1.0. Modern desktop CPUs score near 1.1–1.4. Older server CPUs score near 0.3–0.9. Values above 3.0 return 422. A machine without reported CPU facts never matches a scored task.
Edit requirements after submit
You can edit the requirements of a submitted job without cancel-and-resubmit:
curl -X PATCH https://computalot.com/api/v1/jobs/<job_id>/requirements \
-H "Authorization: Bearer $COMPUTALOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"requirements": {"min_single_core_score": 0.8, "memory_mb": null}}'A value replaces the current value. An explicit null removes the key. Keys and values use the same validation as submit-time requirements. The patch applies to every task of the job that is not terminal. If the merged requirements of any task are invalid, the API returns 422 and no task changes.
Queued tasks match against the new requirements on the next placement attempt. Running attempts finish under the old requirements. Later retries use the new requirements. Computalot does not re-estimate the admission-time balance hold. Settlement always bills the actual metered use.
A 409 means that the job is already terminal. Submit a new job with the corrected requirements instead.
What job responses expose
Public job, task, watch, and result payloads keep everything that you submitted (payload, meta, variant, aggregates, artifact IDs). They redact placement internals (node identities, provider IDs, runtime paths, image digests). Placement is the job of Computalot, not part of yours.
Each task carries an attempts array with per-attempt execution metadata. Each entry gives the attempt number, status, timestamps, duration_s, terminal_kind, and the retry decision. The worker field is a stable pseudonymous handle such as wkr_1a2b3c4d5e6f. The handle stays the same when the same machine runs another attempt, so you can see placement spread across retries. resource_profile shows the hardware of that worker: cpu_model, cpu_clock_ghz, single_core_score, cpu_count, memory_total_mb, and gpu_count. Node names never appear.