Synced from
docs/llm-agent-reference.mdin the Computalot monorepo.
This is the complete reference with tutorials and examples. For a compact version, use GET /llms.txt.
Computalot is a distributed compute platform. You submit typed jobs, and you get structured JSON results. The platform supplies GPU and CPU capacity. Computalot meters each task by the second, at the market rate plus a 35% service markup. The minimum billed time per task-attempt is 60 seconds.
Open access. Any wallet can authenticate (challenge → sign → verify) and fund the account with USDC through x402 or MPP. No approval is necessary. Computalot issues API keys on request through the waitlist at
/. The discovery endpoints are public.
Base URL: https://computalot.com
https://computalot.com/skill.md— agent skill file. Install this first.https://computalot.com/llms.txt— compact referencehttps://computalot.com/llms-full.txt— this full referencehttps://computalot.com/api/v1/docs— machine-readable JSON indexhttps://computalot.com/openapi.json— OpenAPI 3.1 schema of the public APIhttps://computalot.com/docs— human docshttps://computalot.com/docs/pricing— indicative rates and worked cost examples
Recent Contract Changes (2026-07-22)
- Controller-side OCI builds for projects are durable and asynchronous.
POST /pushreturns202withpush_refandstatus_url. The previous revision stays active during the build. The build status showspublishedorfailedwith bounded diagnostics. - Open wallet access: any wallet can authenticate (challenge → sign → verify) and fund the account. An allowlist entry is not necessary. Computalot continues to issue API keys on request through the waitlist.
- Computalot accepts MPP (Machine Payments Protocol, mpp.dev) and x402. Quote
402responses carry aWWW-Authenticate: Paymentchallenge (EVM charge, with a decoded copy in themppblock of the body). To settle a quote, sendAuthorization: Payment <base64url credential>(EIP-3009). A successful payment returns aPayment-Receiptheader. GET /api/v1/account/quotes/:quote_idreturns one quote with its x402 payment requirements. The OpenAPI 3.1 schema is at/openapi.json. A pricing page with indicative market ranges is at/docs/pricing. Public endpoints answerHEADlikeGET.- Job submission accepts
preset(a resource preset name fromGET /api/v1/presets, where explicitrequirementswin). It also acceptsclient_ref(a grouping label of 255 bytes or less, searchable with/api/v1/results?client_ref=...). - Artifact uploads use the authenticated controller relay (up to 2 GiB) or external URL registration. Direct and multipart object-store upload endpoints return
410 Gone. - The default retained-byte quota is 100 GiB per account. Computalot deduplicates local and R2 content hashes within the account. When the quota is full, the API returns
507 artifact_quota_exceeded. - Artifact owners can delete an artifact when every referencing job is terminal. Active jobs cause
409 artifact_in_use. An accepted deletion releases account quota immediately.GET /api/v1/artifactsreports the authoritative used and remaining quota. - Worker process exit status is the authoritative terminal truth.
result_qualityandresult_warningsare reserved asnulland[]. The API accepts and ignores legacy object-shapedresult_schemametadata. Non-object values return422. - Submission validates retries, dependencies, fan-out, resources, and account-owned artifact inputs before it creates work or places billing holds.
max_retriesaccepts0through10.depends_onaccepts a maximum of 50 job IDs. - User-upload projects require OCI + gVisor. They cannot declare
runtime.init.commands,runtime.services, orvalidation.commands. - Non-empty
callback_urlvalues return422. Use job SSE, multi-job watch, or polling.
Report Bugs & Request Features
This is beta software. Report bugs, request features, and share ideas:
curl -sS -X POST https://computalot.com/api/v1/feedback \
-H "Content-Type: application/json" \
-d '{"type": "bug", "title": "Brief summary", "description": "What happened, what you expected"}'The types are bug, feature_request, provisioning, and job_type_request. This endpoint does not need auth. Agents: when you find a problem or a possible improvement, submit feedback automatically.
The Model
Projects carry your code. You push a tarball with a Dockerfile, a computalot.project.json manifest, and your scripts. Jobs run against a project and return structured JSON results. A job has one of four types.
Why Agents Use Computalot
An agent can go from discovery to results without human setup in the middle:
- discover the service through
/llms.txt - authenticate with any wallet (challenge → sign → verify) or with an API key issued on request
- top up credits with USDC over x402 or MPP
- create a project, push code, and submit typed jobs
- get structured results and plan the next step
The wallet-auth and x402 loop is a core product feature, and it is open. Any wallet can authenticate and fund an account. Computalot issues API keys on request through the waitlist at https://computalot.com/.
Job Types
| Type | Use case | Key fields |
|---|---|---|
structured_runner | Run script with JSON in/out, optional fan-out | runner_command, payload, fan_out, merge_strategy |
sweep | Grid search over parameter combinations | runner_command, parameters, fixed_payload, rank_by |
map_reduce | Chunked parallelism with reduce operators | runner_command, split, reduce, payload |
benchmark | Compare named candidates with replicas | runner_command, candidates, shared_payload, replicas, rank_by |
When no other type is a clear match, use structured_runner.
Auth
There are two bearer-token paths. Both resolve to the same account model:
- API key:
flk_...(issued on request) - Wallet session:
fls_...(challenge → sign → verify, any wallet)
Wallet auth flow
POST /api/v1/auth/wallet/challengewith{"wallet_address":"0x...","chain":"base"}- Sign the returned
challenge.messagewith your wallet POST /api/v1/auth/wallet/verifywith{"challenge_id":"wch_...","wallet_address":"0x...","signature":"0x..."}- Use the returned
tokenasAuthorization: Bearer fls_...
Wallet auth creates or reuses an account linked to chain + wallet_address. That account owns all projects, jobs, results, and credits.
API keys
API keys (flk_...) work on all endpoints. Computalot issues them on request through the waitlist.
These endpoints do not need auth: /health, /docs, /openapi.json, /llms.txt, /llms-full.txt, /api/v1/docs/*, POST /api/v1/feedback, POST /api/v1/auth/register (returns 403 with access guidance), POST /api/v1/auth/wallet/challenge, POST /api/v1/auth/wallet/verify.
GET /metrics is for operators only. It accepts local requests, admin auth, or a dedicated metrics token.
Billing
Computalot uses account credits. A job reserves a bounded hold for the initial attempt plus the requested max_retries budget. The hold settles to actual usage when the job becomes terminal. Infrastructure requeues do not consume the configured retry budget.
Computalot meters each task per second at the live market rate plus a 35% service markup. The minimum billed time per task-attempt is 60 seconds. The summary.billing_estimate field in the submit response is the authoritative quote for the job. Computalot never charges for queue time. timeout_s caps the runtime cost of each task. Indicative class ranges and worked examples are at /docs/pricing.
API keys and wallet sessions authenticate the same account model and the same billing surfaces.
GET /api/v1/account/balance— read creditsGET /api/v1/account/ledger— transaction historyGET /api/v1/account/holds— active holds- Project init is free. It requires an available balance of $5.
- To fund with x402, create a quote with
POST /api/v1/account/quotes/topup. Pay it. Then settle withPOST /api/v1/account/quotes/:id/pay/x402.
Account billing endpoints
Authenticated callers can inspect billing state with:
GET /api/v1/account/balanceGET /api/v1/account/ledgerGET /api/v1/account/holdsGET /api/v1/account/quotesGET /api/v1/account/quotes/:quote_id— one quote, including its x402 payment requirements
The billing truth is on GET /api/v1/account/balance, GET /api/v1/account/holds, GET /api/v1/account/ledger, and GET /api/v1/account/quotes.
GET /api/v1/account/balance returns the main numbers for a client:
ledger_balance_usd: total credited minus settled debitsheld_usd: funds currently reserved for active jobsavailable_usd: spendable balance after holdsactive_hold_countopen_quote_count
How pricing works in practice
Think in quotes and holds, not in hidden infrastructure details.
Before Computalot admits a job, it derives a submit-time estimate from:
- the requested job type
- the planned task count and fan-out shape
- the requested
requirements - the requested
timeout_s - the resolved
reliability_mode
This estimate becomes the hold. If the account cannot cover the hold, Computalot rejects the job before it starts. After admission, the job can finish inside that reserved exposure. Computalot does not stop an admitted job for routine billing reasons.
Submit-time billing summary
Job submit responses can include:
summary.billing_estimatesummary.billing_admissionsummary.billing_hold
Important fields include:
- the inferred
resource_class - the inferred
runtime_class - the resolved
reliability_mode estimated_hold_usd- whether the account had enough available balance to admit the job
For long or expensive jobs, treat the submit response as the authoritative pricing signal for that exact request.
Funding flow (x402 or MPP)
The funding rail for autonomous wallets speaks two HTTP-402 payment protocols over the same quotes: x402 and MPP (Machine Payments Protocol, https://mpp.dev/). Both settle the same EIP-3009 USDC authorization. Use the protocol that your wallet tooling supports.
- Create a quote with
POST /api/v1/account/quotes/topupand a requested amount such as{ "amount_usd": 5.0 }. The cap per top-up is$10,000. For larger funding, submit several smaller top-ups. - Computalot returns
402 Payment Required. - The response advertises the payment both ways: an x402
PAYMENT-REQUIREDheader (mirrored in the body fieldpayment_required) and an MPPWWW-Authenticate: Paymentchallenge (methodevm, intentcharge, decoded copy in themppblock of the body). The top-upquoteis in the body. The MPP challenge id is the quote id. - Settle over either protocol:
- x402: sign
payment_required.accepts[0]and base64-encode the payment payload. Then retryPOST /api/v1/account/quotes/:quote_id/pay/x402with the payload in thePAYMENT-SIGNATUREheader (bearer auth required). Success returnsPAYMENT-RESPONSE. - MPP: sign the same EIP-3009 authorization for the decoded challenge request. Wrap it as an MPP credential (
{challenge, payload: {type: "authorization", from, to, value, validAfter, validBefore, nonce, signature}, source}) and base64url-encode it. Then POST the pay URL (or/topup) withAuthorization: Payment <credential>and no bearer token. Success returns a base64url JSONPayment-Receiptheader.
- x402: sign
- On success, the internal account balance increases. Settlements are replay-safe: repeats return
replay: true.
If a job submit or a project-init gate fails because the balance is too low, Computalot can return the same 402 shape with a shortfall quote. The client can then fund the exact gap and retry.
- If
POST /api/v1/projects/:name/initreturns that shortfall quote, fund the account. Then retryPOST /api/v1/projects/:name/init. - If
POST /api/v1/jobsreturns that shortfall quote, fund the account. Then retry the same submit request.
An agent therefore does not need:
- a subscription
- a stored credit card
- a pre-issued public API key
It can discover, fund, and use compute through the API itself.
Reliability mode
reliability_mode is a public submission field:
best_effortstrict_complete
Use strict_complete for research-sensitive fan-out work: sweeps, benchmarks, CMA generations, and training or evaluation batches. In these runs, one missing task invalidates the outcome.
Execution policy and placement
User projects run in one execution mode with two placement options:
sandboxed(default) — your uploaded project code runs inside a gVisor sandbox.placement_policy = "shared"(default): shares warm workers with other sandboxed tenants.placement_policy = "dedicated": routes work to project-bound dedicated capacity. Admission stays best-effort.
The fields are optional. Omit them to accept the defaults (sandboxed + shared). Other execution_policy values are reserved for platform-internal runtimes. If you submit them with a user project, the API returns 422.
Core Model
- A job is the user-visible unit of work that you submit
- Tasks are the parallel execution units that Computalot creates from your job
- You do not target infrastructure directly. Submit jobs with a project and optional resource requirements. Computalot handles placement
- Terminal jobs and results stay queryable for 30 days
Resource Requirements
Submit minimum resource needs with your job. Computalot places the work on matching capacity:
{
"type": "structured_runner",
"project": "my-ml-project",
"runner_command": ["python", "train.py"],
"payload": {"epochs": 3},
"requirements": {
"cpu": 8,
"memory_mb": 16384,
"storage_gb": 40,
"gpu_count": 1,
"gpu_memory_mb": 12288,
"profile": "gpu"
}
}requirementsare minimums. Computalot can place the work on larger machines.profile:"cpu"or"gpu". CPU jobs can spill onto idle GPU capacity.min_single_core_scorefloors relative single-core CPU speed (baseline 1.0, max 3.0). Modern desktop CPUs score near 1.1–1.4, older server CPUs near 0.3–0.9. Use it for single-thread-bound tasks.- Best-effort queueing is the default. An explicit
reservation: {"mode":"best_effort"}is equal to an omitted field. guaranteed,parallelism,guaranteed_for_s, andmax_wait_sreturn422until Computalot implements atomic reservation admission, expiry, and audit.distribution.max_tasks_per_nodealso returns422until the lease query can enforce it atomically.
How to request capacity well:
- Ask for minimum real requirements. Oversized requests shrink eligible capacity and increase queue time.
- Use
profile: "gpu"only when the task needs GPU compute. - The first job on a newly pushed revision can pay a cold-start cost while Computalot prepares matching capacity.
- Use
POST /api/v1/projects/:name/initonly when you want to prepare currently available workers before a burst. - If one missing task corrupts the final result set, use
reliability_mode: "strict_complete". - Treat the submit response as the pricing signal for that exact request. Computalot derives the hold estimate from this shape.
Journey 1: Sign Up → First Job → Results
This journey shows the public end-to-end path from zero to a completed job. It uses wallet auth and account credits.
This is the canonical self-serve onboarding path. Any wallet works, and no approval is necessary. The steps are wallet sign-in, billing reads, funding (x402 or MPP), project setup, job submit, and result retrieval.
Prerequisites
# Base URL
export BASE_URL="https://computalot.com"
# Your wallet address
export WALLET_ADDRESS="0x1234567890abcdef1234567890abcdef12345678"If you already have an admin-issued API key, you can skip the wallet flow and set:
export TOKEN="flk_..."Otherwise, use wallet auth.
Step 1: Authenticate with your wallet
Request a challenge:
CHALLENGE_JSON=$(curl -sS "$BASE_URL/api/v1/auth/wallet/challenge" \
-X POST \
-H "Content-Type: application/json" \
-d "{\"wallet_address\":\"$WALLET_ADDRESS\",\"chain\":\"base\"}")
echo "$CHALLENGE_JSON"Sign challenge.message with your wallet provider or SDK. Then verify:
export CHALLENGE_ID="wch_..."
export SIGNATURE="0xSIGNED_MESSAGE"
VERIFY_JSON=$(curl -sS "$BASE_URL/api/v1/auth/wallet/verify" \
-X POST \
-H "Content-Type: application/json" \
-d "{
\"challenge_id\":\"$CHALLENGE_ID\",
\"wallet_address\":\"$WALLET_ADDRESS\",
\"signature\":\"$SIGNATURE\"
}")
echo "$VERIFY_JSON"Extract the returned session token and use it for the remaining steps:
export TOKEN="fls_..."Step 2: Fund your account if needed
Read your balance. If you need more detail, inspect the other billing truth surfaces:
curl -sS "$BASE_URL/api/v1/account/balance" \
-H "Authorization: Bearer $TOKEN"
curl -sS "$BASE_URL/api/v1/account/holds" \
-H "Authorization: Bearer $TOKEN"
curl -sS "$BASE_URL/api/v1/account/ledger" \
-H "Authorization: Bearer $TOKEN"
curl -sS "$BASE_URL/api/v1/account/quotes" \
-H "Authorization: Bearer $TOKEN"If available_usd is less than the minimum funded floor, request a top-up quote:
curl -sS "$BASE_URL/api/v1/account/quotes/topup" \
-X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"amount_usd": 5.0,
"description": "initial project setup and first job"
}'This request returns 402 Payment Required. An x402-capable client then pays and retries:
curl -sS "$BASE_URL/api/v1/account/quotes/<quote_id>/pay/x402" \
-X POST \
-H "Authorization: Bearer $TOKEN" \
-H "PAYMENT-SIGNATURE: <x402 payment payload>"An MPP-capable client (mpp.dev) settles the same quote from the WWW-Authenticate: Payment challenge instead. No bearer token is necessary:
curl -sS "$BASE_URL/api/v1/account/quotes/<quote_id>/pay/x402" \
-X POST \
-H "Authorization: Payment <base64url MPP credential>"
# success carries a base64url JSON Payment-Receipt response headerIf project init or job submit later returns a shortfall quote, fund the account. Then retry the same blocked request.
Step 3: Register your project
curl -sS "$BASE_URL/api/v1/projects" \
-X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "my-ml-project",
"remote_dir": "/root/my-ml-project",
"env": {"CLICKHOUSE_HOST": "db.internal"},
"setup_timeout_s": 1200
}'Computalot extracts your code into remote_dir. env is an optional map of runtime env vars. setup_timeout_s overrides the default 600s setup timeout.
Step 4: Create your project files
Your project needs a Dockerfile, computalot.project.json, and your code:
my-ml-project/
├── Dockerfile
├── computalot.project.json
└── job.pyProjects run as sandboxed OCI containers. See the Project Manifest docs for the full manifest schema.
FROM python:3.11-slim
WORKDIR /workspace
COPY . .# job.py
import json, os
payload = json.load(open(os.environ["COMPUTALOT_TASK_PAYLOAD"]))
result = {"status": "ok", "source": "getting-started"}
json.dump(result, open(os.environ["COMPUTALOT_TASK_RESULT"], "w"))Minimal manifest:
{
"version": 1,
"runtime": {
"kind": "oci",
"sandbox": "gvisor",
"workdir": "/workspace"
},
"entrypoint": {
"command": ["python", "job.py"]
}
}Full schema and examples: https://computalot.com/docs/projects/project-manifest
Step 5: Upload the project
cd my-ml-project
tar czf ../code.tar.gz .
# Upload the tarball as the raw request body
curl -sS "$BASE_URL/api/v1/projects/my-ml-project/push" \
-X POST \
-H "Authorization: Bearer $TOKEN" \
--data-binary @../code.tar.gzAfter a successful push, the latest revision is published immediately. You can submit jobs immediately. The first job can take longer while Computalot prepares the runtime on demand.
Optional: to prepare currently available workers before a burst, call init:
curl -sS "$BASE_URL/api/v1/projects/my-ml-project/init" \
-X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{}'To inspect the published state and the warm state, read the project status:
curl -sS "$BASE_URL/api/v1/projects/my-ml-project/status" \
-H "Authorization: Bearer $TOKEN"can_accept_new_jobs: true means that the latest revision is published. You can submit jobs immediately. ready_for_jobs: true means that Computalot finished platform-side runtime preparation. Neither field proves that your application-level imports or credentials work. Use manifest validation checks. After setup changes, run one small smoke job.
GET /api/v1/projects/:name/status is the top-level readiness truth for the active revision. After a successful push, the new content hash is visible immediately. You can submit jobs immediately, even while that revision warms.
Before a burst, if you want the already-warm signal, wait for:
can_accept_new_jobs: trueinit_state: "ready"ready_for_jobs: true
Step 6: Submit a job
curl -sS "$BASE_URL/api/v1/jobs" \
-X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "structured_runner",
"runner_command": ["python3", "job.py"],
"payload": {"test_case": "getting-started"},
"project": "my-ml-project",
"timeout_s": 120,
"requirements": {
"cpu": 1,
"memory_mb": 256,
"profile": "cpu"
},
"reliability_mode": "strict_complete"
}'The submit response can include:
summary.billing_estimatesummary.billing_admissionsummary.billing_hold
Treat this response as the authoritative estimate for that exact request.
Step 7: Read results and billing state
# Job status
curl -sS "$BASE_URL/api/v1/jobs/<job_id>" \
-H "Authorization: Bearer $TOKEN"
# Structured results
curl -sS "$BASE_URL/api/v1/results/<job_id>" \
-H "Authorization: Bearer $TOKEN"
# Aggregated stdout/stderr
curl -sS "$BASE_URL/api/v1/jobs/<job_id>/output" \
-H "Authorization: Bearer $TOKEN"
# Billing state after the run
curl -sS "$BASE_URL/api/v1/account/balance" \
-H "Authorization: Bearer $TOKEN"
curl -sS "$BASE_URL/api/v1/account/holds" \
-H "Authorization: Bearer $TOKEN"
curl -sS "$BASE_URL/api/v1/account/ledger" \
-H "Authorization: Bearer $TOKEN"GET /api/v1/results/<job_id> is the canonical result surface. It returns raw per-task results plus top-level summary, aggregate_result, aggregate_aliases, completeness, result_persisted, and output_persisted. For weighted fan-out jobs, summary also carries direct alias fields such as avg_edge. It also carries coverage fields such as weight_field, expected_weight, completed_weight, and pending_weight. The response keeps public submission metadata such as meta and variant. Each task can include project_content_hash, so you know which project version produced it.
GET /api/v1/jobs/<job_id>/output is the live aggregated diagnostics surface. During auto-retry, it keeps the output and error of the most recent failed attempt until the next attempt emits its own diagnostics. Jobs therefore do not go blank between retries. If a worker or runtime failure happens before your command starts, the visible text can be platform preflight stderr, not user-process stdout.
Step 8: Update your project
Use PUT /api/v1/projects/:name for metadata only. For code changes, push a new tarball. The revision is published immediately and can accept jobs. Use invalidate only to discard old prepared runtimes. Use init only to pre-warm currently available workers:
tar czf ../code.tar.gz .
curl -sS "$BASE_URL/api/v1/projects/my-ml-project/push" \
-X POST \
-H "Authorization: Bearer $TOKEN" \
--data-binary @../code.tar.gz
curl -sS "$BASE_URL/api/v1/projects/my-ml-project/invalidate" \
-X POST \
-H "Authorization: Bearer $TOKEN"
curl -sS "$BASE_URL/api/v1/projects/my-ml-project/init" \
-X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{}'If POST /api/v1/projects/:name/push returns 202, the tarball and the OCI build record are durable, but the revision is not published yet. Poll the returned status_url, GET /api/v1/projects/:name/pushes/:push_ref, or GET /api/v1/projects/:name/push until status is published or failed. The previous published revision stays active during the build. Failed pushes expose a structured error and a bounded log_tail.
If POST /api/v1/projects/:name/push returns 409, another push or an active initialization is already in flight. When the response includes active_push.status_url, use it. Otherwise poll GET /api/v1/projects/:name/status until initialization settles. Then retry.
If the uploaded body is gzip but not a valid tarball, POST /api/v1/projects/:name/push returns 422 with error: "invalid tarball" instead of a generic server error.
If computalot.project.json references project files, command working directories, build inputs, or named cache mounts that do not exist, POST /api/v1/projects/:name/push returns 422 before it accepts the new version.
When Computalot can read the previous tarball locally, a successful push response also includes tarball_diff with added_files, removed_files, and changed_files. Clients can then catch incomplete uploads immediately.
The same push response can already show the active-revision transition for the new code. Relevant fields are ready_for_jobs: false, status_message, next_action, and init_status.init_state: "refreshing".
Journey 2: Fan-Out Parallelism
Use the supported fan_out shapes for parallel work. The public API rejects a top-level tasks array. This rule prevents per-task command and routing overrides from bypassing the validated runner contract.
Use when: you want to run the same script on many inputs in parallel. Examples: model evaluation, agent swarms, batch processing, CMA generations.
Runner script
Your script reads $COMPUTALOT_TASK_PAYLOAD and writes to $COMPUTALOT_TASK_RESULT:
# evaluate.py
import json, os
payload = json.load(open(os.environ["COMPUTALOT_TASK_PAYLOAD"]))
model_name = payload["model"]
score = run_evaluation(model_name, payload["dataset"])
with open(os.environ["COMPUTALOT_TASK_RESULT"], "w") as f:
json.dump({"model": model_name, "score": score}, f)Option A: Fan-out by values
Split a list into one task per item:
curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://computalot.com/api/v1/jobs \
-d '{
"type": "structured_runner",
"runner_command": ["python", "evaluate.py"],
"payload": {
"models": ["model-a", "model-b", "model-c", "model-d"],
"config": {"n_trials": 100}
},
"fan_out": {"by": "models", "batch_size": 2},
"merge_strategy": "keyed",
"project": "my-ml-project",
"timeout_s": 600
}'This submission creates 4 tasks. Each task receives {"models": "model-a", "config": {"n_trials": 100}}.
With batch_size (or batch_per_task), Computalot groups several fan-out items into one dispatched task and adds payload._batch metadata. For fan_out.by, the split field becomes the sub-list for that task.
Computalot does not inject project KV values automatically. Read them through the project KV API before submission. Then put the resolved values directly in payload.
Option B: Fan-out by explicit items (CMA / evolutionary)
One explicit payload per candidate. You control the payloads exactly:
{
"type": "structured_runner",
"runner_command": ["python", "evaluate.py"],
"fan_out": {"items": [
{"params": [0.1, 0.5, 0.3], "generation": 12},
{"params": [0.2, 0.4, 0.6], "generation": 12},
{"params": [0.3, 0.3, 0.1], "generation": 12}
]},
"project": "my-proj"
}This submission creates 3 tasks, one per item. The client (your optimizer) owns the state between generations.
Option C: Fan-out by chunks
Split a numeric range into N chunks:
curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://computalot.com/api/v1/jobs \
-d '{
"type": "structured_runner",
"runner_command": ["python", "simulate.py"],
"payload": {"total_seeds": 10000},
"fan_out": {"chunks": 20, "range_field": "total_seeds", "total": 10000},
"merge_strategy": "collect",
"project": "my-ml-project",
"timeout_s": 1800
}'This submission creates 20 tasks. Each task receives its chunk, for example {"start": 0, "count": 500}.
Fan-out contract
The supported public fan_out shapes are:
{"fan_out": {"by": "field"}}{"fan_out": {"items": [{...}, {...}]}}{"fan_out": {"chunks": N, "total": N, ...}}
These shapes are mutually exclusive. A request that mixes by, items, or chunks + total returns 422. Select exactly one shape before you retry the submit.
Merge strategies
"collect"(default) — all results in one list"keyed"— results indexed by a key from the payload of each task (requiresfan_out.by)"weighted_avg"— the weighted average of a numeric field (set bothvalue_fieldandweight_field)
Result quality
result_quality and result_warnings are reserved response fields. They currently return null and [], and they do not affect task or job status. The API accepts and ignores legacy object-shaped result_schema metadata. Non-object values return 422. Validate output inside the runner. Use the process exit status as the terminal truth.
Journey 3: Parameter Search (Sweep)
Use when: you want to run every combination of parameters and rank the results. Examples: grid search, hyperparameter tuning.
curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://computalot.com/api/v1/jobs \
-d '{
"type": "sweep",
"runner_command": ["python", "evaluate.py"],
"project": "my-ml-project",
"parameters": {
"learning_rate": [0.001, 0.01, 0.1],
"batch_size": [32, 64, 128]
},
"fixed_payload": {"dataset": "cifar10", "epochs": 5},
"rank_by": "accuracy",
"rank_order": "desc",
"timeout_s": 3600
}'This submission creates 9 tasks (3x3 cartesian product). Each task receives this payload:
{"learning_rate": 0.001, "batch_size": 32, "dataset": "cifar10", "epochs": 5, "_sweep_idx": 0, "_sweep_params": {"learning_rate": 0.001, "batch_size": 32}}Your runner writes the rank_by field to $COMPUTALOT_TASK_RESULT:
import json, os
payload = json.load(open(os.environ["COMPUTALOT_TASK_PAYLOAD"]))
accuracy = train_and_evaluate(payload["learning_rate"], payload["batch_size"])
with open(os.environ["COMPUTALOT_TASK_RESULT"], "w") as f:
json.dump({"accuracy": accuracy}, f)The result is a ranked leaderboard in the job summary:
{
"results": [
{"params": {"learning_rate": 0.01, "batch_size": 64}, "result": {"accuracy": 0.95}, "rank": 1},
{"params": {"learning_rate": 0.001, "batch_size": 128}, "result": {"accuracy": 0.92}, "rank": 2}
],
"best": {"params": {"learning_rate": 0.01, "batch_size": 64}, "result": {"accuracy": 0.95}, "rank": 1}
}Key fields: parameters (map → value lists, max 1000 combos), fixed_payload, rank_by (required), rank_order ("desc" default or "asc").
Journey 4: GPU Training with Live Progress
Use when: you run a long training job and want real-time progress updates plus resumable checkpoints.
JOB_ID=$(curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://computalot.com/api/v1/jobs \
-d '{
"type": "structured_runner",
"runner_command": ["python", "train.py"],
"payload": {"epochs": 100, "batch_size": 32},
"project": "my-ml-project",
"timeout_s": 7200,
"requirements": {"profile": "gpu", "gpu_count": 1, "gpu_memory_mb": 16384},
"checkpointing": {"enabled": true, "resume_from_latest": true}
}' | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
# Stream progress (SSE)
curl -sS -N -H "Authorization: Bearer $TOKEN" \
https://computalot.com/api/v1/jobs/$JOB_ID/streamThe stream starts with a snapshot. Then it emits job, task, and event deltas. Running-task frames include live_feedback.output_tail. You can therefore show rolling stdout/stderr before the job finishes.
Report progress from your training script:
import json, os
payload = json.load(open(os.environ["COMPUTALOT_TASK_PAYLOAD"]))
resume_state = payload.get("_resume") or {}
start_epoch = resume_state.get("epoch", 0)
for epoch in range(start_epoch, 100):
loss = train_one_epoch()
# Computalot captures COMPUTALOT_PROGRESS lines and streams them to the SSE endpoint
print(
f"COMPUTALOT_PROGRESS:{json.dumps({'epoch': epoch, 'loss': loss, 'percent': epoch})}",
flush=True,
)Save model artifacts to $COMPUTALOT_ARTIFACT_DIR:
import os, shutil
model_path = os.path.join(os.environ['COMPUTALOT_ARTIFACT_DIR'], 'model.pt')
torch.save(model.state_dict(), model_path)Artifact IDs appear in the task result. Download them with GET /api/v1/artifacts/:id.
If you include a checkpoint object in progress or result payloads, Computalot persists the latest checkpoint. When checkpointing.resume_from_latest is enabled, Computalot injects the checkpoint back into _resume on retry. When Computalot can durably publish the checkpoint as an artifact, task state also records artifact_id, artifact_source, publish_status, and published_at. Retries then rewrite _resume.checkpoint.path to the downloaded local checkpoint file automatically.
For live UIs, combine:
GET /api/v1/jobs/:id/stream— SSE updates for one jobGET /api/v1/jobs/watch?ids=id1,id2,...— one SSE connection for 2-100 jobs with terminal summaries, aggregate fields, and persistence flagsGET /api/v1/jobs/:id/tasks— per-tasklive_feedback,latest_progress,checkpoint,resume_state,runtime_s,health_status, plus the preserved last failed attempt while a retry is queued or runningGET /api/v1/results/:job_id— the canonical terminal result surface with per-task payload/result/output presence, completeness, and artifact IDsGET /api/v1/jobs/:id/output— aggregated stdout/stderr that keeps the most recent failed attempt until the current attempt emits new diagnosticsGET /api/v1/jobs/:id— job-levelfeedback_summaryand checkpoint summary
Public job, task, watch, and result payloads keep the submitted payload, meta, variant, aggregate fields, and artifact IDs. They redact placement-only fields such as current_node, provider IDs, runtime paths, and image refs and digests.
Each task also carries an attempts array with per-attempt execution metadata, in attempt order. Each entry gives the attempt number, public status, leased_at/started_at/completed_at, duration_s, terminal_kind, and a curated retry decision (decision, decision_reason, category, budget). The worker field is a stable pseudonymous handle such as wkr_1a2b3c4d5e6f — the same machine keeps the same handle across attempts, so placement spread across retries is visible without exposing node identity. resource_profile shows the hardware of that worker: cpu_model, cpu_clock_ghz, single_core_score, cpu_count, memory_total_mb, and gpu_count.
Journey 5: Multi-Stage Pipelines
Use when: step 2 depends on the output of step 1.
# Step 1: Train
JOB1=$(curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://computalot.com/api/v1/jobs \
-d '{
"type": "structured_runner",
"runner_command": ["python", "train.py"],
"payload": {"epochs": 50, "lr": 0.001},
"project": "my-ml-project",
"timeout_s": 7200,
"gpu_required": true
}' | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
# Step 2: Evaluate (waits for step 1)
curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://computalot.com/api/v1/jobs \
-d "{
\"type\": \"structured_runner\",
\"runner_command\": [\"python\", \"evaluate.py\"],
\"payload\": {\"model_path\": \"/root/my-ml-project/model.pt\"},
\"depends_on\": [\"$JOB1\"],
\"project\": \"my-ml-project\",
\"timeout_s\": 600
}"Step 2 stays queued until step 1 completes. If step 1 fails, step 2 auto-cancels.
Passing files between stages (Artifacts)
# Upload artifact after step 1
ART_ID=$(curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/octet-stream" \
-H "X-Artifact-Filename: model.pt" \
--data-binary @model.pt \
https://computalot.com/api/v1/artifacts | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
# Reference in step 2's payloadOr use _artifacts.download in the payload for automatic pre-task download:
{
"payload": {
"_artifacts": {"download": {"dataset": "art_abc123"}},
"model_type": "base"
}
}Computalot resolves downloads before task execution. The env var COMPUTALOT_ARTIFACT_<key> points to the local path.
For staged pipelines, read the concrete artifact ID of the upstream job from GET /api/v1/results/:job_id. Then submit the downstream job with that ID:
{
"depends_on": ["job_setup_123"],
"payload": {
"_artifacts": {
"download": {
"dataset": "art_abc123"
}
}
}
}For long ML or evaluation jobs, do not stop at _artifacts.download alone:
- Use manifest
data_sourcesfor immutable remote inputs that Computalot must prepare before launch. - Use manifest
cache_mountsfor writable caches that your code creates at runtime. - If the input is on Hugging Face and must stay read-only, use
data_sources[].source = "huggingface"withdelivery = "mount". The worker then useshf-mount. - If the runner downloads from Hugging Face itself, add a
huggingfacecache mount. ThenHF_HOMEandTRANSFORMERS_CACHEpersist per worker. Ad hoc runtime downloads do not usehf-mountautomatically.
For smaller coordination data, read project-scoped shared state before submission. Put the returned value directly in the payload:
curl -sS -X PUT \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://computalot.com/api/v1/projects/my-project/kv/dataset_ready \
-d '{"value": {"status": "ready"}}'curl -sS -H "Authorization: Bearer $TOKEN" \
https://computalot.com/api/v1/projects/my-project/kv/dataset_readypayload._shared.resolve and {job_id, artifact} download references are not supported. They return 422. The API does not silently ignore them.
Journey 6: Comparing Strategies (Benchmark)
Use when: you want to compare two or more named strategies, with replicas for statistical significance.
curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://computalot.com/api/v1/jobs \
-d '{
"type": "benchmark",
"runner_command": ["python", "evaluate.py"],
"project": "my-project",
"candidates": {
"strategy_a": {"model": "gpt4", "temperature": 0.7},
"strategy_b": {"model": "claude", "temperature": 0.5},
"baseline": {"model": "random"}
},
"shared_payload": {"dataset": "test_set_v3", "n_trials": 100},
"replicas": 3,
"rank_by": "score",
"timeout_s": 1800
}'This submission creates 9 tasks (3 candidates x 3 replicas). Each task receives this payload:
{"dataset": "test_set_v3", "n_trials": 100, "model": "gpt4", "temperature": 0.7, "_candidate": "strategy_a", "_replica": 1}To vary a field per replica: "replica_vary": {"field": "seed_base", "stride": 1000}.
The result is a leaderboard with per-candidate statistics:
{
"leaderboard": [
{"candidate": "strategy_a", "mean": 0.92, "std": 0.03, "min": 0.89, "max": 0.95, "count": 3, "rank": 1},
{"candidate": "strategy_b", "mean": 0.85, "std": 0.02, "min": 0.83, "max": 0.87, "count": 3, "rank": 2},
{"candidate": "baseline", "mean": 0.50, "std": 0.05, "min": 0.45, "max": 0.55, "count": 3, "rank": 3}
]
}Key fields: candidates (map, min 2), shared_payload, replicas (default 1, max 100), rank_by (required), rank_order.
Sweep vs Benchmark: use sweep to explore a parameter grid. Use benchmark to compare named alternatives with replicas.
Journey 7: Monte Carlo / Simulations (Map-Reduce)
Use when: you want to split a range into chunks, process the chunks in parallel, and aggregate with operators.
curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://computalot.com/api/v1/jobs \
-d '{
"type": "map_reduce",
"runner_command": ["python", "evaluate_seeds.py"],
"project": "my-project",
"payload": {"strategy": "momentum"},
"split": {"field": "seed", "start": 0, "total": 10000, "chunks": 50},
"reduce": {
"total_pnl": "sum",
"sharpe_ratio": "weighted_avg:sample_count",
"max_drawdown": "max"
},
"timeout_s": 7200
}'This submission creates 50 tasks. Each task receives {"strategy": "momentum", "seed_start": 0, "seed_count": 200}.
For non-contiguous ranges, use explicit split.ranges instead of start + total + chunks:
{
"type": "map_reduce",
"runner_command": ["python", "evaluate_seeds.py"],
"project": "my-project",
"payload": {"strategy": "momentum"},
"split": {
"field": "seed",
"ranges": [
{"start": 860791000, "count": 1000},
{"start": 200000000, "count": 1000},
{"start": 500000000, "count": 1000}
]
},
"reduce": {
"avg_edge": "mean"
}
}Your runner:
import json, os
payload = json.load(open(os.environ["COMPUTALOT_TASK_PAYLOAD"]))
results = run_simulation(payload["strategy"], payload["seed_start"], payload["seed_count"])
with open(os.environ["COMPUTALOT_TASK_RESULT"], "w") as f:
json.dump({"total_pnl": results.pnl, "sharpe_ratio": results.sharpe, "max_drawdown": results.drawdown, "sample_count": payload["seed_count"]}, f)The result is the reduced values in the job summary:
{
"reduced": {
"total_pnl": 15234.50,
"sharpe_ratio": 1.87,
"max_drawdown": 0.23
}
}Key fields: split ({field, start, total, chunks}), reduce (map of field → operator).
Reduce operators: sum, mean, max, min, weighted_avg:<weight_field>, concat, count, collect.
Heavy Jobs (Large Data, Checkpoints, Training)
For GB-scale datasets, large checkpoints, and long training runs:
Inputs:
- Keep
payloadsmall. Do not embed large data in JSON. - Use
_artifacts.downloadfor large inputs. Workers download and cache the files before launch. - The resolved paths are in
payload._artifacts.local_paths. Single-file downloads also receiveCOMPUTALOT_ARTIFACT_<NAME>env vars.
Outputs:
- Write checkpoints and files to
$COMPUTALOT_ARTIFACT_DIR. - Use
_artifacts.uploadfor named uploads. Paths must be relative regular files under$COMPUTALOT_ARTIFACT_DIR. The API rejects absolute paths, symlinks, and traversal outside that root. - If a JSON result is too large, Computalot spills it to an artifact. It then returns
result_spilled,result_artifact_id, andresult_filenamethrough the authenticated relay path.
Operational:
- Set
timeout_sto more than the expected runtime, with margin. - Submit jobs after a push. Use
POST /api/v1/projects/:name/initonly to prepare currently available workers before a burst. - Write checkpoints and outputs to
$COMPUTALOT_ARTIFACT_DIR. Use$COMPUTALOT_TASK_SCRATCH_DIRor$TMPDIRfor temp files. - Use external or object storage for multi-GB datasets and model bundles.
Shared build cache ($COMPUTALOT_TASK_CACHE_DIR):
- A cache directory for each project. Build outputs, compiled dependencies, and downloaded toolchains persist in it across sequential tasks on a worker.
- Workers currently admit one active task at a time, because per-task CPU and memory cgroups are not enforced yet. Still publish cache entries atomically. Then an interrupted task cannot leave a partial final file.
Project Setup
Projects run as sandboxed OCI containers. The lifecycle is:
POST /api/v1/projects— registerPOST /api/v1/projects/:name/push— upload a tarball with the Dockerfile,computalot.project.json, and your codePOST /api/v1/jobs— submit work against the published revision- Optional:
POST /api/v1/projects/:name/init— prepare currently available workers GET /api/v1/projects/:name/status— inspect the published state and the warm state
- Project init is free. It requires an available balance of $5.
- Init is asynchronous.
- After a push,
can_accept_new_jobscan already betruewhileready_for_jobsis stillfalse. - After a code change, push the new tarball. Use
invalidateonly to discard old prepared runtimes.
Project structure
my-project/
├── Dockerfile
├── computalot.project.json
├── requirements.txt
└── job.pyDockerfile
Install dependencies in your Dockerfile:
FROM python:3.11-slim
WORKDIR /workspace
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .computalot.project.json
See the Project Manifest docs for the full schema.
Tips
- After setup changes, run one small smoke job before you submit a large batch
- Use manifest
validation.executablesandvalidation.filesfor declarative runtime checks - Put dependencies and build steps in the Dockerfile. User uploads cannot declare
runtime.init.commands,runtime.services, orvalidation.commands
Public project endpoints
POST /api/v1/projectsPUT /api/v1/projects/:namefor metadata-only updatesPOST /api/v1/projects/:name/pushPOST /api/v1/projects/:name/initPOST /api/v1/projects/:name/invalidateGET /api/v1/projects/:name/statusGET /api/v1/projects/:name/status/detailsGET /api/v1/projects
Debugging failed setup
curl -sS -H "Authorization: Bearer $TOKEN" \
https://computalot.com/api/v1/projects/my-project/status
curl -sS -H "Authorization: Bearer $TOKEN" \
https://computalot.com/api/v1/projects/my-project/status/detailsCorrect your Dockerfile or manifest, and push a new revision. The invalidate and init calls below are optional. Use them to discard prepared state and pre-warm currently available workers:
tar czf code.tar.gz . && \
curl -sS -X POST -H "Authorization: Bearer $TOKEN" --data-binary @code.tar.gz \
https://computalot.com/api/v1/projects/my-project/push && \
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
https://computalot.com/api/v1/projects/my-project/invalidate && \
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://computalot.com/api/v1/projects/my-project/init -d '{}'Runner Protocol (All Job Types)
All runner-based types use this contract:
- Computalot launches your
runner_commandwith a task-specific payload - Computalot writes the payload to a temp file. The path is in
$COMPUTALOT_TASK_PAYLOAD - The result file path is in
$COMPUTALOT_TASK_RESULT - Your script writes a JSON result to
$COMPUTALOT_TASK_RESULT - For progress, print
COMPUTALOT_PROGRESS:{json}to stdout
COMPUTALOT_*env vars are the Computalot runtime protocol.
Task APIs and SSE streams surface normal stdout/stderr live through live_feedback.output_tail. If your runner wraps another process, keep the child unbuffered or flush explicitly. Computalot can then forward logs promptly.
Task env order: base runtime → project env files (.computalot.env, computalot.env, .env) → project env map → meta.env overrides. If .venv/bin/python exists, Computalot prepends .venv/bin to PATH.
Exit codes: exit code 0 means success. A non-zero exit code means failure. Computalot captures the last ~1000 characters as the error (the tail, not the head, which keeps tracebacks). Computalot stores the full output (up to 10 KB) per task.
Payload varies by type:
- structured_runner: the
payloadfield. Chunk fan-out adds{start, count}. - sweep:
fixed_payload+ parameter combination +_sweep_idx+_sweep_params - map_reduce:
payload+{field_start, field_count}chunk boundaries - benchmark:
shared_payload+ candidate config +{_candidate, _replica}
Allowed executables: python, python3, node, deno, bun, ruby, julia, Rscript, uv, pip, npm, npx, cargo, rustc. Computalot blocks shell executables (bash, sh, zsh).
Job Lifecycle
Statuses: planning → queued → running → completed | partial | failed | cancelled
- Terminal states:
completed,partial,failed,cancelled - Poll:
GET /api/v1/jobs/:idevery 2-5s until terminal - Stream:
GET /api/v1/jobs/:id/streamfor SSE updates - Multi-job watch:
GET /api/v1/jobs/watch?ids=id1,id2,...for one SSE stream that covers 2-100 jobs - Canonical terminal results:
GET /api/v1/results/:job_id - Per-task progress and retry continuity:
GET /api/v1/jobs/:id/tasks - Aggregated output continuity:
GET /api/v1/jobs/:id/output - Cancel:
PUT /api/v1/jobs/:id/cancelwith{"reason": "..."} - Edit requirements:
PATCH /api/v1/jobs/:id/requirementswith{"requirements": {...}}. A value replaces the current value, and an explicitnullremoves the key. The patch applies to every non-terminal task, with submit-time validation on each merged result — one invalid result rejects the whole patch with422. Queued tasks match against the new requirements on the next placement attempt. Running attempts finish under the old requirements, and later retries use the new ones. The admission-time hold is not re-estimated.409= the job is already terminal. - Auto-retry: set
max_retriesfrom 0 through 10 on submission. A task that fails on its own (non-zero exit) requeues up to N times before the task and job fail. When other capacity is available, a retry runs on a different machine than the one that failed the task. The billing hold covers the full requested attempt budget. Attempt history stays inGET /api/v1/jobs/:id/events. - Infrastructure failures never consume your retry budget. Exit 137 (OOM, SIGKILL, preemption) and lost workers requeue automatically with the budget refunded (
attempt.infra_requeuedandattempt.lostevents). Repeated exit-137 deaths mean that the task needs more memory, not that your code is wrong. - If only your code can see that the machine is broken, write
"failure_class": "infra"(and an optionalfailure_reason) 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, and prefers a different machine. Over this cap, the failure counts as a normal workload failure. Each attempt still bills for its runtime. - Terminal worker events update task and job state through the authoritative control-plane path. Node-loss recovery is fallback repair, not a normal delay for clients.
timeout_scaps the runtime of each task after worker start. Internal controller policy handles queue age. Queue age is not part of the user runtime budget.partial= at least one task completed and at least one task failed or was cancelledcompleted= every task completed successfully.failed= no task completed successfully and execution ended in failure- For research-sensitive runs where partial completion is not acceptable, use
strict_complete. - Optional
priority: "high" | "normal" | "low"biases scheduling between otherwise comparable jobs. - Submit responses can include hold and admission metadata. The client can then reason about cost before execution starts.
- Public job, task, watch, and result payloads keep the submitted
payload,meta,variant, aggregate fields, and artifact IDs. They redact placement-only fields such ascurrent_node, provider IDs, runtime paths, and image refs and digests.
Result Presence
result_presentandoutput_presentinGET /api/v1/results/:job_idreport whether durable result and output data exists.summary.aggregate_result,summary.aggregate_aliases, andsummary.completenessexpose aggregation coverage.result_qualityandresult_warningsare reserved and currently returnnulland[].- The API accepts and ignores legacy object-shaped
result_schemametadata. Non-object values return422. Runner exit status is authoritative: exit0completes, non-zero fails.
Job Tags & Filtering
{"type": "sweep", "tags": ["experiment_42", "lr_search"], ...}Filter: GET /api/v1/jobs?tag=experiment_42. The tag filter matches jobs whose tags array contains the exact value.
GET /api/v1/jobs and GET /api/v1/results accept more filters for search and client re-sync:
status=completed,failed— one status or a comma-separated list.terminalexpands to the four terminal statuses.since=2026-08-06T12:00:00Z— jobs withsubmitted_atat or after the cutoff. A date such as2026-08-06reads as midnight UTC. Pass your last sync time to fetch only new jobs.ids=job_a,job_b— a specific set of job IDs in one call.project=,type=,client_ref=— exact-match filters.
Unknown status values and malformed since values return 422 with a recommended_action.
Job Priority
{"priority": "high"}Use high for latency-sensitive fan-out or evaluation jobs that must start before ordinary background work. Use low for background submissions. The default is normal.
Batch Submission
Up to 200 jobs in one request:
curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://computalot.com/api/v1/jobs/batch \
-d '{"jobs": [
{"type": "structured_runner", "runner_command": ["python", "eval.py"], "project": "my-proj", "payload": {"lr": 0.001}, "tags": ["sweep_72"]},
{"type": "structured_runner", "runner_command": ["python", "eval.py"], "project": "my-proj", "payload": {"lr": 0.01}, "tags": ["sweep_72"]}
]}'Response: 201 (all ok) or 207 (partial): {jobs, submitted, errors, error_count}.
Completion Notifications
Computalot does not currently support webhook callbacks. The API rejects a non-empty callback_url with 422. A submission therefore cannot silently opt into notifications that Computalot will not deliver. Use GET /api/v1/jobs/:id/stream, GET /api/v1/jobs/watch?ids=..., or poll GET /api/v1/jobs/:id for terminal status.
Job Dependencies (DAG)
{"depends_on": ["job_20260312_143000_abc123"]}depends_on accepts a maximum of 50 account-accessible job IDs. Computalot does not dispatch tasks until all dependencies reach completed or partial. If a dependency fails or is cancelled, dependent jobs cancel automatically.
Streaming Progress
SSE for one job
curl -sS -N -H "Authorization: Bearer $TOKEN" \
https://computalot.com/api/v1/jobs/<job_id>/streamThe stream starts with snapshot. Then it emits incremental job, task, and event frames. It ends with done.
Running-task frames include live_feedback.output_tail, which is the fastest public surface for live stdout/stderr.
SSE for multiple jobs
curl -sS -N -H "Authorization: Bearer $TOKEN" \
"https://computalot.com/api/v1/jobs/watch?ids=<id1>,<id2>,<id3>"The maximum is 100 jobs. Idle periods emit ping. The stream ends with done when all jobs are terminal.
Terminal job frames include client_ref, tags, meta, variant, summary, aggregate_result, aggregate_aliases, completeness, and result_persisted / output_persisted when available. A client can therefore often skip a follow-up result fetch. For weighted fan-out jobs, fields such as avg_edge can be present directly in the terminal SSE payload.
SSE for a whole project
curl -sS -N -H "Authorization: Bearer $TOKEN" \
"https://computalot.com/api/v1/projects/<project>/stream"One connection covers all jobs in a project. Reconnect after the 1-hour timeout.
Common Agent Patterns
Poll for completion:
while true; do
STATUS=$(curl -sS -H "Authorization: Bearer $TOKEN" \
https://computalot.com/api/v1/jobs/<job_id> | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
case $STATUS in
completed|partial|failed|cancelled) break ;;
esac
sleep 5
doneRead structured results:
curl -sS -H "Authorization: Bearer $TOKEN" https://computalot.com/api/v1/results/<job_id>Cancel a job:
curl -sS -X PUT -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://computalot.com/api/v1/jobs/<job_id>/cancel \
-d '{"reason":"no longer needed"}'Update project code:
tar czf code.tar.gz . && \
curl -sS -X POST -H "Authorization: Bearer $TOKEN" --data-binary @code.tar.gz \
https://computalot.com/api/v1/projects/my-project/push && \
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
https://computalot.com/api/v1/projects/my-project/invalidate && \
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://computalot.com/api/v1/projects/my-project/init -d '{}'Debugging Failed Jobs
- Job error:
GET /api/v1/jobs/:id—errorandrecommended_action - Per-task details:
GET /api/v1/jobs/:id/tasks—error,output(up to 10 KB), and a structured failureresultwithfailure_kindandexit_code. For long jobs, it also carrieslatest_progress,checkpoint, andresume_state. During auto-retry, queued and running tasks keep the diagnostics of the most recent failed attempt until the current attempt emits its own output. - Live stream:
GET /api/v1/jobs/:id/stream— SSE updates - Timeline:
GET /api/v1/jobs/:id/events— state change events - Project readiness:
GET /api/v1/projects/:name/status - Diagnostics:
GET /api/v1/projects/:name/status/details - Billing state:
GET /api/v1/account/balance,GET /api/v1/account/holds,GET /api/v1/account/ledger
| Symptom | Cause | Fix |
|---|---|---|
402 Payment Required on top-up or shortfall flow | account needs more credits | pay the returned quote (x402 or MPP) and retry |
| project init rejected before setup starts | available balance less than the funded floor | top up to at least $5, then retry init |
| ”No native library found” | Missing system library | Correct the Dockerfile, push, invalidate, re-init |
| exit_code_1, useless error | Truncated error | Read the per-task output field (full 10 KB). |
| task looks blank while retrying | the current attempt did not emit anything yet | Read GET /api/v1/jobs/:id/output or GET /api/v1/jobs/:id/tasks for the preserved diagnostics of the last failed attempt. |
| task failed before user code printed anything | worker or runtime preflight failed first | The visible output / error can be platform stderr, not user stdout. |
| Cargo/Rust toolchain broken | Computalot worker fault | Wait for auto-recovery. Your code is not the cause |
| Tasks stuck in queued | Cold start or capacity catch-up | Read the project status and job diagnostics. The first job can wait while runtime preparation happens on demand |
| Project ready but tasks fail | The Dockerfile misses dependencies, or declarative checks do not cover application behavior | Correct the Dockerfile and run a small smoke job |
| 401 / DB timeout after warmup | Credentials error | Run a small smoke job to make sure that credentials work before a large batch |
Artifact API
The artifact store is content-addressed, and it passes files between jobs. The supported public paths are controller-relayed uploads (up to 2 GiB) and registration of existing external URLs. Direct and multipart object-store uploads return 410 Gone. Relay uploads enforce account-scoped ownership and retained-byte quota accounting.
# Upload through the controller (streaming, max 2 GiB)
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/octet-stream" \
-H "X-Artifact-Filename: dataset.parquet" \
--data-binary @dataset.parquet \
https://computalot.com/api/v1/artifacts
# Register external URL (no upload)
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://computalot.com/api/v1/artifacts/external \
-d '{"url": "https://s3.example.com/data.parquet", "filename": "data.parquet"}'
# Download
curl -sS -H "Authorization: Bearer $TOKEN" \
https://computalot.com/api/v1/artifacts/<id> -o output.bin
# List
curl -sS -H "Authorization: Bearer $TOKEN" \
https://computalot.com/api/v1/artifactsArtifacts stay retained, and they count against the default 100 GiB account quota until you delete them. The quota counts each local or R2 content hash once per account. Duplicate metadata rows therefore do not multiply retained bytes. A relay upload that exceeds retained capacity returns HTTP 507 with code artifact_quota_exceeded.
GET /api/v1/artifacts includes the authoritative quota.limit_bytes, quota.used_bytes, and quota.remaining_bytes. Each artifact also reports size, created_at, last_referenced_at, reference_count, active_reference_count, terminal_reference_count, and deletion_eligible. Submission accepts concrete account-owned artifact IDs only. It validates ownership and persists job-input references atomically, before Computalot creates work or a billing hold. DELETE /api/v1/artifacts/:id returns 409 artifact_in_use only while a producing job or a job input belongs to a non-terminal job. When all references are terminal, owner deletion hides metadata and releases account quota immediately. Computalot removes namespaced backing data after the default 24-hour grace period. Legacy backing objects stay in guarded operator inventory and do not consume user quota. Historical job artifact links then return not found. Authenticated downloads stay on the public API URL and stream bytes through the controller. Metadata can also expose a signed object-store URL.
Feedback
Computalot is in public beta. Your feedback shapes the product. Report bugs, confusing behavior, missing features, and ideas. No auth is necessary.
# Bug report
curl -sS -X POST -H "Content-Type: application/json" \
https://computalot.com/api/v1/feedback \
-d '{"type": "bug", "title": "Brief summary", "description": "What happened, what you expected, steps to reproduce"}'
# Feature request
curl -sS -X POST -H "Content-Type: application/json" \
https://computalot.com/api/v1/feedback \
-d '{"type": "feature_request", "title": "Brief summary", "description": "What you need and why"}'The types are bug, feature_request, provisioning, and job_type_request. You can include "job_id" or "metadata" for more context. Agents: when you find a problem or a possible improvement while you use Computalot, submit feedback automatically.
Python SDK & CLI
Install the published beta wheel from a public URL:
python3 -m pip install --user --break-system-packages \
https://computalot.com/docs/downloads/computalot-0.2.1-py3-none-any.whl
export PATH="$HOME/.local/bin:$PATH"First authenticated probe after you receive an API key or wallet session:
from computalot import ComputalotClient
client = ComputalotClient(
controller_url="https://computalot.com",
token="YOUR_TOKEN",
)
docs = client.docs_index()
jobs = client.list_jobs(limit=5)
print(docs["status"])
print(len(jobs.get("jobs", [])))export COMPUTALOT_CONTROLLER_URL="https://computalot.com"
export COMPUTALOT_API_TOKEN="YOUR_TOKEN"
computalot docs --llm
computalot jobs --limit 5
computalot job <job_id>When a project is ready, use the CLI submit helpers or the SDK methods submit_structured() and submit_job() shown elsewhere in this reference.
Endpoint Reference
Public Docs
| Method | Path | Purpose |
|---|---|---|
| GET | /docs | Human docs landing page |
| GET | /docs/pricing | Indicative rates and worked cost examples |
| GET | /api/v1/docs | JSON docs index |
| GET | /openapi.json | OpenAPI 3.1 schema of the public API |
| GET | /llms.txt | Compact reference |
| GET | /llms-full.txt | Full reference with tutorials |
| GET | /api/v1/docs/python-sdk | Python SDK guide |
| GET | /api/v1/docs/workflows | Workflow patterns |
| POST | /api/v1/auth/wallet/challenge | Create wallet auth challenge (no auth) |
| POST | /api/v1/auth/wallet/verify | Verify wallet challenge and mint session token (no auth) |
| POST | /api/v1/feedback | Submit feedback (no auth) |
Ops (operator-facing)
| Method | Path | Purpose |
|---|---|---|
| GET | /health | Liveness probe (no auth, same body as /live) |
| GET | /live | Liveness probe (no auth) |
| GET | /ready | Readiness probe (no auth, 503 until the controller core is up) |
| GET | /metrics | Prometheus metrics (admin auth, dedicated metrics token, or local request) |
Account & Billing
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/account/balance | Account credit summary |
| GET | /api/v1/account/ledger | Settled ledger entries |
| GET | /api/v1/account/holds | Active and historical holds |
| GET | /api/v1/account/quotes | Funding and shortfall quotes |
| GET | /api/v1/account/quotes/:quote_id | One quote with its x402 requirements and MPP challenge |
| POST | /api/v1/account/quotes/topup | Create a top-up quote (402 Payment Required, also settles MPP credentials) |
| POST | /api/v1/account/quotes/:quote_id/pay/x402 | Settle a quote — x402 PAYMENT-SIGNATURE or MPP Authorization: Payment |
Job API
| Method | Path | Purpose |
|---|---|---|
| POST | /api/v1/jobs | Submit a job |
| POST | /api/v1/jobs/batch | Submit up to 200 jobs |
| GET | /api/v1/jobs | List jobs (?status=&project=&tag=&limit=50&offset=0) |
| GET | /api/v1/jobs/:id | Full job state with feedback_summary and checkpoint summary |
| GET | /api/v1/jobs/:id/output | Stdout/stderr, with the preserved last-failed-attempt diagnostics during retries |
| GET | /api/v1/jobs/:id/tasks | Per-task details, errors, live feedback, checkpoint state, and preserved retry diagnostics |
| GET | /api/v1/jobs/:id/events | Lifecycle events |
| GET | /api/v1/jobs/:id/metrics | Aggregate metrics |
| GET | /api/v1/jobs/:id/stream | SSE stream for one job, with task live_feedback.output_tail deltas |
| GET | /api/v1/jobs/watch?ids=a,b,c | SSE stream for multiple jobs (max 100, with ping keepalives, metadata, and persistence flags) |
| GET | /api/v1/projects/:name/stream | SSE stream for all jobs in a project |
| PUT | /api/v1/jobs/:id/cancel | Cancel a job |
| PATCH | /api/v1/jobs/:id/requirements | Edit the requirements of all non-terminal tasks. A value replaces the current value, a null removes the key |
Results & Artifacts
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/results/:job_id | Per-task results plus metadata, aggregate_result, aggregate_aliases, completeness, and persistence flags. Use job and task endpoints for live retry-loop diagnostics |
| GET | /api/v1/results | List recent terminal results (?limit=20&offset=0, paginated). Filters: job_id, ids, project, client_ref, tag, user_id, group_by, include_tasks. Malformed limit/offset returns 422. |
| POST | /api/v1/artifacts | Relay upload artifact (raw body, max 2 GiB) |
| POST | /api/v1/artifacts/external | Register external URL artifact |
| GET | /api/v1/artifacts | List artifacts with the authoritative quota limit, used, and remaining bytes |
| GET | /api/v1/artifacts/:id | Download artifact (authenticated requests stay on the public API URL, and metadata can expose a signed object-store URL) |
| GET | /api/v1/artifacts/:id/meta | Artifact metadata |
| DELETE | /api/v1/artifacts/:id | Delete when all referencing jobs are terminal. Active references return 409 artifact_in_use |
Project API
| Method | Path | Purpose |
|---|---|---|
| POST | /api/v1/projects | Register project (name, remote_dir, optional env and setup_timeout_s) |
| GET | /api/v1/projects | List projects |
| GET | /api/v1/projects/:name | Project configuration + readiness status |
| PUT | /api/v1/projects/:name | Update project metadata only (owner only) |
| DELETE | /api/v1/projects/:name | Delete project (owner only) |
| POST | /api/v1/projects/:name/push | Upload tarball (raw gzip, not multipart, max 100 MB). Controller OCI builds return a durable 202 + status_url. Returns 409 during another push/init, 422 for malformed tarballs or invalid manifest references |
| GET | /api/v1/projects/:name/push | Read the latest durable push/build status, image identity, structured error, and bounded log tail |
| GET | /api/v1/projects/:name/pushes/:push_ref | Read one durable push/build status by reference |
| PUT | /api/v1/projects/:name/cancel-queued | Cancel queued/planning jobs for one project (optional tag filter) |
| POST | /api/v1/projects/:name/init | Prepare currently available workers (async) |
| POST | /api/v1/projects/:name/invalidate | Force re-init |
| GET | /api/v1/projects/:name/status | Project readiness |
| GET | /api/v1/projects/:name/status/details | Readiness + diagnostics |
| GET | /api/v1/projects/:name/kv | List project-scoped shared state entries |
| PUT | /api/v1/projects/:name/kv/:key | Write project-scoped shared state value |
| GET | /api/v1/projects/:name/kv/:key | Read project-scoped shared state value |
| DELETE | /api/v1/projects/:name/kv/:key | Delete project-scoped shared state value |
| GET | /api/v1/projects/:name/stream | SSE stream for project jobs |
Other
| Method | Path | Purpose |
|---|---|---|
| POST | /api/v1/auth/register | Disabled self-service API-key issuance (403) with waitlist + beta-access guidance |
| GET | /api/v1/presets | Resource presets |