Synced from
docs/llm-agent-reference.mdin the Computalot monorepo.
Complete reference with tutorials and examples. For a compact version, use GET /llms.txt.
Computalot is a distributed compute platform. Submit typed jobs, get structured JSON results. GPU and CPU capacity, metered by the task-second at market rates.
Open access. Any wallet can authenticate (challenge → sign → verify) and fund the account with USDC via x402 or MPP — no approval needed. API keys are issued on request via the waitlist at
/. Discovery endpoints are public.
Base URL: https://computalot.com
https://computalot.com/skill.md— install this skill to get startedhttps://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-15)
- Open wallet access: any wallet can authenticate (challenge → sign → verify) and fund the account — no allowlist entry required. API keys remain issued on request via the waitlist.
- MPP (Machine Payments Protocol, mpp.dev) is accepted alongside x402: quote
402s carry aWWW-Authenticate: Paymentchallenge (EVM charge; decoded copy in the body’smppblock), quotes settle withAuthorization: Payment <base64url credential>(EIP-3009), and success returns aPayment-Receiptheader. GET /api/v1/account/quotes/:quote_idreturns one quote (with its x402 payment requirements); the OpenAPI 3.1 schema is served at/openapi.json; a pricing page with indicative market ranges lives at/docs/pricing; public endpoints answerHEADlikeGET.- Job submission accepts
preset(resource preset name fromGET /api/v1/presets; explicitrequirementswin) andclient_ref(≤255-byte grouping label, searchable via/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. Local/R2 content hashes are deduplicated within the account; quota exhaustion returns
507 artifact_quota_exceeded. - Artifact owners can delete once every referencing job is terminal. Active jobs return
409 artifact_in_use; accepted deletion releases account quota immediately, andGET /api/v1/artifactsreports authoritative used/remaining quota. - Worker process exit status is authoritative terminal truth.
result_quality/result_warningsare reserved asnull/[]; non-emptyresult_schemasubmissions return422. - Submission validates retries, dependencies, fan-out, resources, and account-owned artifact inputs before creating work or placing billing holds.
max_retriesaccepts0through10;depends_onaccepts at most 50 job IDs. - User-upload projects require OCI + gVisor and 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. Please 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"}'Types: bug, feature_request, provisioning, job_type_request. No auth required. Agents: when you hit an issue or see room for improvement, submit feedback automatically.
The Model
Projects carry your code: push a tarball with a Dockerfile, a computalot.project.json manifest, and your scripts. Jobs run against a project as one of four typed shapes and return structured JSON results.
Why Agents Use Computalot
Computalot is built so an agent can go from discovery to results without a human doing setup in the middle:
- discover the service through
/llms.txt - authenticate with any wallet (challenge → sign → verify) or an API key issued on request
- top up credits with USDC over x402 or MPP
- create a project, push code, and submit typed jobs
- retrieve structured results and decide what to do next
That wallet-auth + x402 loop is a core product feature and is open: any wallet can authenticate and fund an account. API keys are issued on request via 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 |
Default to structured_runner unless another type clearly fits.
Auth
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 and are issued on request via the waitlist.
No auth required: /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 operator-gated: local requests, admin auth, or a dedicated metrics token only.
Billing
Computalot uses account credits. Jobs reserve a bounded hold for the initial attempt plus the requested max_retries budget and settle to actual usage when the job becomes terminal. Infrastructure requeues do not consume the configured retry budget.
Pricing is metered market-rate compute per task-second: the submit response’s summary.billing_estimate is the authoritative per-job quote, queue time is never charged, and timeout_s caps each task’s runtime cost. Indicative class ranges and worked examples: /docs/pricing.
API keys and wallet sessions authenticate the same account model and the same billing surfaces.
GET /api/v1/account/balance— check creditsGET /api/v1/account/ledger— transaction historyGET /api/v1/account/holds— active holds- Project init is free but requires $5 available balance
- Fund via x402:
POST /api/v1/account/quotes/topup→ pay →POST /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
Billing truth lives 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 a client should care about:
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
Users should think in terms of quotes and holds, not hidden infrastructure details.
Before a job is admitted, Computalot derives a submit-time estimate from:
- the requested job type
- planned task count and fan-out shape
- requested
requirements - requested
timeout_s - resolved
reliability_mode
That estimate becomes the hold. If the account cannot cover it, the job is rejected before it starts. Once admitted, the job is allowed to finish inside that reserved exposure instead of being killed mid-flight for routine billing reasons.
Submit-time billing summary
Job submit responses can include:
summary.billing_estimatesummary.billing_admissionsummary.billing_hold
Important fields include:
- inferred
resource_class - inferred
runtime_class - resolved
reliability_mode estimated_hold_usd- whether the account had enough available balance to admit the job
For long-running 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 whichever your wallet tooling supports.
POST /api/v1/account/quotes/topupwith a requested amount such as{ "amount_usd": 5.0 }— the per-top-up cap is$10,000, submit multiple smaller top-ups for larger funding- Computalot returns
402 Payment Required - The response advertises the payment both ways: an x402
PAYMENT-REQUIREDheader (mirrored in the body’spayment_required) and an MPPWWW-Authenticate: Paymentchallenge (methodevm, intentcharge; decoded copy in the body’smppblock). The top-upquoteis in the body; the MPP challenge id is the quote id. - Settle over either protocol:
- x402: sign
payment_required.accepts[0], base64-encode the payment payload, and retryPOST /api/v1/account/quotes/:quote_id/pay/x402with it 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}), base64url-encode it, and POST the pay URL (or/topup) withAuthorization: Payment <credential>— no bearer token needed. 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 project-init gate fails for insufficient balance, Computalot can return the same 402 shape with a shortfall quote so the client can fund the exact gap and retry.
- If
POST /api/v1/projects/:name/initreturns that shortfall quote, fund the account and retryPOST /api/v1/projects/:name/init. - If
POST /api/v1/jobsreturns that shortfall quote, fund the account and retry the same submit request.
In practice, this means an agent 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/evaluation batches where missing one task would invalidate 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): reuses warm workers with other sandboxed tenants.placement_policy = "dedicated": routes work to project-bound dedicated capacity. Admission is still best effort.
The fields are optional — leave them out to accept the defaults (sandboxed + shared). Other execution_policy values are reserved for platform-internal runtimes; submitting them with a user project returns 422.
Core Model
- A job is the user-visible unit of work you submit
- Tasks are the parallel execution units Computalot creates from your job
- You do not target infrastructure directly — submit jobs with a project and optional resource requirements, and Computalot handles placement
- Terminal jobs and results are queryable for 30 days
Resource Requirements
Submit minimum resource needs with your job — Computalot places 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 may place on larger machines.profile:"cpu"or"gpu". CPU jobs can spill onto idle GPU capacity.- Best-effort queueing is the default. An explicit
reservation: {"mode":"best_effort"}is equivalent to omitting the field. guaranteed,parallelism,guaranteed_for_s, andmax_wait_sreturn 422 until atomic reservation admission, expiry, and audit are implemented.distribution.max_tasks_per_nodealso returns 422 until the lease query enforces 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 truly needs GPU compute. - The first job on a newly pushed revision may pay a cold-start cost while matching capacity is prepared.
- Use
POST /api/v1/projects/:name/initonly when you explicitly want to prepare currently available workers ahead of time before a burst. - Use
reliability_mode: "strict_complete"when missing one task would corrupt the final result set. - Treat the submit response as the pricing signal for that exact request. The hold estimate is derived from this shape.
Journey 1: Sign Up → First Job → Results
This walks through the public end-to-end path from zero to a completed job using wallet auth and account credits.
This is the canonical self-serve onboarding path — any wallet works, no approval needed: wallet sign-in, billing checks, 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
Ask for 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
Check balance and inspect the same billing truth surfaces if you need more detail:
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 below 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"
}'That returns 402 Payment Required. An x402-capable client should then pay and retry:
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 needed:
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 instead of admitting the request, fund the account and 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
}'remote_dir is where Computalot extracts your code. 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 right away; the first one may take longer while Computalot prepares runtime on demand.
Optional: if you want to prepare currently available workers ahead of time, call init manually:
curl -sS "$BASE_URL/api/v1/projects/my-ml-project/init" \
-X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{}'Check status when you want to inspect published vs warm state:
curl -sS "$BASE_URL/api/v1/projects/my-ml-project/status" \
-H "Authorization: Bearer $TOKEN"can_accept_new_jobs: true means the latest revision is published and can be submitted immediately. ready_for_jobs: true means Computalot finished platform-side runtime preparation. Neither field proves your application-level imports or credentials are valid — use manifest validation checks and run one small smoke job after setup changes.
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 and jobs can be submitted right away, even if that revision is still warming.
If you want the already-warm signal before a burst, 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 that 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 alias fields like avg_edge directly plus coverage fields such as weight_field, expected_weight, completed_weight, and pending_weight. The response also preserves public submission metadata like meta and variant, and each task may include project_content_hash so you can confirm which project version produced it.
GET /api/v1/jobs/<job_id>/output is the live aggregated diagnostics surface. During auto-retry, it preserves the most recent failed attempt’s output and error until the next attempt emits its own diagnostics, so jobs do not go blank between retries. If a worker/runtime failure happens before your command starts, that visible text can be platform preflight stderr rather than 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, and 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 409, Computalot is already initializing or refreshing that project. Poll GET /api/v1/projects/:name/status until init_state is no longer initializing or refreshing, then retry the push.
If the uploaded body is gzip but not a valid tarball, POST /api/v1/projects/:name/push now returns 422 with error: "invalid tarball" instead of surfacing 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 now returns 422 before the new version is accepted.
When Computalot can read the previous tarball locally, a successful push response also includes tarball_diff with added_files, removed_files, and changed_files so clients can catch incomplete uploads immediately.
That same push response can already show the active-revision transition for the new code via fields such as 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. A top-level tasks array is rejected on the public API because per-task command and routing overrides would bypass the validated runner contract.
Use when: You want to run the same script on many different inputs in parallel — 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
}'Creates 4 tasks. Each gets: {"models": "model-a", "config": {"n_trials": 100}}.
With batch_size (or batch_per_task), Computalot groups multiple fan-out items into one dispatched task and adds payload._batch metadata. For fan_out.by, the split field becomes that task’s sub-list.
Project KV values are not injected automatically. Read them through the project KV API before submission and 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"
}Creates 3 tasks, one per item. The client (your optimizer) owns 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
}'Creates 20 tasks, each with {"start": 0, "count": 500}, etc.
Fan-out contract
Supported public fan_out shapes are:
{"fan_out": {"by": "field"}}{"fan_out": {"items": [{...}, {...}]}}{"fan_out": {"chunks": N, "total": N, ...}}
These shapes are mutually exclusive. Mixing by, items, or chunks + total in one request returns 422; Choose exactly one shape before retrying the submit.
Merge strategies
"collect"(default) — all results in a list"keyed"— results indexed by a key from each task’s payload (requiresfan_out.by)"weighted_avg"— 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 do not affect task or job status. Non-empty result_schema submissions return 422; validate output inside the runner and use process exit status as terminal truth.
Journey 3: Parameter Search (Sweep)
Use when: You want to try every combination of parameters and rank results. 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
}'Creates 9 tasks (3x3 cartesian product). Each task’s 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)Result: 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’re running 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 emits job, task, and event deltas. Running-task frames include live_feedback.output_tail, so you can 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: GET /api/v1/artifacts/:id.
If you include a checkpoint object in progress or result payloads, Computalot persists the latest checkpoint and injects it back into _resume on retry when checkpointing.resume_from_latest is enabled. When the checkpoint can be durably published as an artifact, task state also records fields like artifact_id, artifact_source, publish_status, and published_at, and retries 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 preserves 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/result payloads keep submitted payload, meta, variant, aggregate fields, and artifact IDs, but they redact placement-only fields such as current_node, provider IDs, runtime paths, and image refs/digests.
Journey 5: Multi-Stage Pipelines
Use when: Step 2 depends on step 1’s output.
# 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 (env var COMPUTALOT_ARTIFACT_<key> points to local path).
For staged pipelines, read the upstream job’s concrete artifact ID 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-running ML or evaluation jobs, do not stop at _artifacts.download alone:
- use manifest
data_sourcesfor immutable remote inputs that should be prepared before launch - use manifest
cache_mountsfor writable caches your code creates at runtime - if the input lives on Hugging Face and should stay read-only, prefer
data_sources[].source = "huggingface"withdelivery = "mount"so the worker useshf-mount - if the runner downloads from Hugging Face itself, add a
huggingfacecache mount soHF_HOMEandTRANSFORMERS_CACHEpersist per worker; ad hoc runtime downloads do not usehf-mountautomatically
For smaller coordination data, read project-scoped shared state before submission and 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 and return 422 rather than being silently ignored.
Journey 6: Comparing Strategies (Benchmark)
Use when: You want to compare 2+ 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
}'Creates 9 tasks (3 candidates x 3 replicas). Each 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}.
Result: 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 for exploring a parameter grid. Use benchmark for comparing named alternatives with replicas for statistical confidence.
Journey 7: Monte Carlo / Simulations (Map-Reduce)
Use when: You want to split a range into chunks, process 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
}'Creates 50 tasks. Each gets: {"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)Result: 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 before launch. - Resolved paths:
payload._artifacts.local_paths. Single-file downloads also getCOMPUTALOT_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; absolute paths, symlinks, and traversal outside that root are rejected. - If a JSON result is too large, Computalot spills it to an artifact and returns
result_spilled,result_artifact_id, andresult_filenamethrough the authenticated relay path.
Operational:
- Set
timeout_sabove expected runtime with margin. - Submit jobs after push; use
POST /api/v1/projects/:name/initonly if you want to prepare currently available workers ahead of time. - Write checkpoints and outputs to
$COMPUTALOT_ARTIFACT_DIR. Use$COMPUTALOT_TASK_SCRATCH_DIRor$TMPDIRfor temp files. - Use external/object storage for multi-GB datasets and model bundles.
Shared build cache ($COMPUTALOT_TASK_CACHE_DIR):
- A per-project cache directory whose build outputs, compiled dependencies, and downloaded toolchains persist across sequential tasks on a worker.
- Workers currently admit one active task at a time because per-task CPU and memory cgroups are not yet enforced. Still publish cache entries atomically so interrupted tasks 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 tarball with Dockerfile +computalot.project.json+ 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 published vs warm state
- Project init is free but requires $5 available balance
- Init is asynchronous
- After a push,
can_accept_new_jobscan already be true even whileready_for_jobsis still false - After a code change, push the new tarball; use
invalidateonly if you want 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
- Run one small smoke job after setup changes before submitting 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/detailsFix your Dockerfile/manifest and push a new revision. The following invalidate and init calls are optional when you explicitly want 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 task-specific payload - Payload written to temp file; path in
$COMPUTALOT_TASK_PAYLOAD - Result file path in
$COMPUTALOT_TASK_RESULT - Your script writes JSON result to
$COMPUTALOT_TASK_RESULT - Progress: print
COMPUTALOT_PROGRESS:{json}to stdout
COMPUTALOT_*env vars are the Computalot runtime protocol.
Normal stdout/stderr is surfaced live through live_feedback.output_tail on task APIs and SSE streams. If your runner wraps another process, keep the child unbuffered or flush explicitly so Computalot can 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: 0 = success, non-zero = failure. Last ~1000 chars captured as error (tail, not head — preserves tracebacks). Full output (up to 10 KB) stored 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. Shell executables (bash, sh, zsh) are blocked.
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 covering 2-100 jobs - Canonical terminal results:
GET /api/v1/results/:job_id - Per-task progress + 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": "..."} - 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/job fail; the billing hold covers the full requested attempt budget and attempt history stays inGET /api/v1/jobs/:id/events. - Infra failures never consume your retry budget: exit 137 (OOM/SIGKILL/preemption) and lost workers requeue automatically with the budget refunded (
attempt.infra_requeued/attempt.lostevents). Repeated exit-137 deaths mean the task needs more memory, not that your code is wrong - Terminal worker events update task and job state through the authoritative control-plane path. Node-loss recovery is fallback repair, not a normal delay clients should wait through
timeout_sis a per-task runtime cap after worker start; queue age is handled by internal controller policy and is not part of the user payload runtime budgetpartial= 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 failurestrict_completeis the recommended mode for research-sensitive runs where partial completion is not acceptable- Optional
priority: "high" | "normal" | "low"biases scheduling between otherwise comparable jobs. - Submit responses can include hold and admission metadata so the client can reason about cost before execution starts
- Public job/task/watch/result payloads keep submitted
payload,meta,variant, aggregate fields, and artifact IDs, but they redact placement-only fields such ascurrent_node, provider IDs, runtime paths, and image refs/digests
Result Presence
result_presentandoutput_presentinGET /api/v1/results/:job_idreport whether durable result/output data exists.summary.aggregate_result,summary.aggregate_aliases, andsummary.completenessexpose aggregation coverage.result_qualityandresult_warningsare reserved and currently returnnulland[].- Non-empty
result_schemasubmissions 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.
Job Priority
{"priority": "high"}Use high for latency-sensitive fan-out or evaluation jobs that should start ahead of ordinary background work. Use low for background submissions. Jobs default to 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
Webhook callbacks are not currently supported. A non-empty callback_url is rejected with 422 so a submission cannot silently opt into notifications that will not be delivered. 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 at most 50 account-accessible job IDs. Tasks are not dispatched until all dependencies reach completed or partial. If a dependency fails or is cancelled, dependent jobs auto-cancel.
Streaming Progress
SSE for one job
curl -sS -N -H "Authorization: Bearer $TOKEN" \
https://computalot.com/api/v1/jobs/<job_id>/streamStarts with snapshot, then incremental job, task, event frames. 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>"Max 100 jobs. Idle periods emit ping. Ends with done when all terminal.
Terminal job frames include client_ref, tags, meta, variant, summary, aggregate_result, aggregate_aliases, completeness, and result_persisted / output_persisted when available, so a client can often avoid a follow-up result fetch. For weighted fan-out jobs that means fields like 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 for all jobs in a project. Reconnect after 1h 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), structured failureresultwithfailure_kind,exit_code, pluslatest_progress,checkpoint, andresume_statefor long-running jobs. During auto-retry, queued/running tasks keep the most recent failed attempt’s diagnostics visible 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 below funded floor | top up to at least $5, then retry init |
| ”No native library found” | Missing system library | Fix Dockerfile, push, invalidate, re-init |
| exit_code_1, useless error | Truncated error | Check per-task output field (full 10 KB). |
| task looks blank while retrying | current attempt has not emitted anything yet | Check GET /api/v1/jobs/:id/output or GET /api/v1/jobs/:id/tasks for the preserved last failed attempt diagnostics. |
| task failed before user code printed anything | worker/runtime preflight failed first | The visible output / error can be platform stderr rather than user stdout. |
| Cargo/Rust toolchain broken | Computalot worker issue | Wait for auto-recovery, not your code |
| Tasks stuck in queued | Cold start or capacity catch-up | Check project status and job diagnostics; the first job may be waiting while runtime preparation happens on demand |
| Project ready but tasks fail | Dockerfile missing deps or application behavior not covered by declarative checks | Fix the Dockerfile and run a small smoke job |
| 401 / DB timeout after warmup | Credentials issue | Verify credentials in a small smoke job before a large batch |
Artifact API
Content-addressed store for passing files between jobs. 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 remain retained and count against the default 100 GiB account quota until deleted. Quota counts each local/R2 content hash once per account, so duplicate metadata rows do not multiply retained bytes. A relay upload that would exceed retained capacity returns HTTP 507 with code artifact_quota_exceeded.
GET /api/v1/artifacts includes authoritative quota.limit_bytes, quota.used_bytes, and quota.remaining_bytes, plus size, created_at, last_referenced_at, reference_count, active_reference_count, terminal_reference_count, and deletion_eligible per artifact. Submission accepts concrete account-owned artifact IDs only, verifies ownership, and persists job-input references atomically before work or a billing hold is created. DELETE /api/v1/artifacts/:id returns 409 artifact_in_use only while a producing job or job input belongs to a non-terminal job. Once all references are terminal, owner deletion hides metadata and releases account quota immediately; namespaced backing data is removed after the default 24-hour grace, while legacy backing objects remain in guarded operator inventory without consuming 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 may also expose a signed object-store URL.
Feedback
Computalot is in public beta. Your feedback directly shapes the product. Please report bugs, confusing behavior, missing features, and ideas. No auth required.
# 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"}'Types: bug | feature_request | provisioning | job_type_request. You can optionally include "job_id" or "metadata" for additional context. Agents: when you encounter an issue or see room for improvement while using 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>Once a project is ready, use the CLI submit helpers or the SDK submit_structured() / submit_job() methods 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 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 + its x402 requirements and MPP challenge |
| POST | /api/v1/account/quotes/topup | Create top-up quote (402 Payment Required; also settles MPP credentials) |
| POST | /api/v1/account/quotes/:quote_id/pay/x402 | Settle 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, including 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, including 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 |
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/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 → 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 authoritative quota limit/used/remaining bytes |
| GET | /api/v1/artifacts/:id | Download artifact (authenticated requests stay on the public API URL; metadata may expose a signed object-store URL) |
| GET | /api/v1/artifacts/:id/meta | Artifact metadata |
| DELETE | /api/v1/artifacts/:id | Delete once 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, setup_timeout_s) |
| GET | /api/v1/projects | List projects |
| GET | /api/v1/projects/:name | Project config + 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; returns 409 during active refresh, 422 for malformed tarballs or invalid manifest references, and may include tarball_diff on success) |
| 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 |