Skip to Content

Synced from docs/llm-agent-reference.md in 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 reference
  • https://computalot.com/llms-full.txt — this full reference
  • https://computalot.com/api/v1/docs — machine-readable JSON index
  • https://computalot.com/openapi.json — OpenAPI 3.1 schema of the public API
  • https://computalot.com/docs — human docs
  • https://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 /push returns 202 with push_ref and status_url. The previous revision stays active during the build. The build status shows published or failed with 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 402 responses carry a WWW-Authenticate: Payment challenge (EVM charge, with a decoded copy in the mpp block of the body). To settle a quote, send Authorization: Payment <base64url credential> (EIP-3009). A successful payment returns a Payment-Receipt header.
  • GET /api/v1/account/quotes/:quote_id returns 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 answer HEAD like GET.
  • Job submission accepts preset (a resource preset name from GET /api/v1/presets, where explicit requirements win). It also accepts client_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/artifacts reports the authoritative used and remaining quota.
  • Worker process exit status is the authoritative terminal truth. result_quality and result_warnings are reserved as null and []. The API accepts and ignores legacy object-shaped result_schema metadata. Non-object values return 422.
  • Submission validates retries, dependencies, fan-out, resources, and account-owned artifact inputs before it creates work or places billing holds. max_retries accepts 0 through 10. depends_on accepts a maximum of 50 job IDs.
  • User-upload projects require OCI + gVisor. They cannot declare runtime.init.commands, runtime.services, or validation.commands.
  • Non-empty callback_url values return 422. 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

TypeUse caseKey fields
structured_runnerRun script with JSON in/out, optional fan-outrunner_command, payload, fan_out, merge_strategy
sweepGrid search over parameter combinationsrunner_command, parameters, fixed_payload, rank_by
map_reduceChunked parallelism with reduce operatorsrunner_command, split, reduce, payload
benchmarkCompare named candidates with replicasrunner_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

  1. POST /api/v1/auth/wallet/challenge with {"wallet_address":"0x...","chain":"base"}
  2. Sign the returned challenge.message with your wallet
  3. POST /api/v1/auth/wallet/verify with {"challenge_id":"wch_...","wallet_address":"0x...","signature":"0x..."}
  4. Use the returned token as Authorization: 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 credits
  • GET /api/v1/account/ledger — transaction history
  • GET /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 with POST /api/v1/account/quotes/:id/pay/x402.

Account billing endpoints

Authenticated callers can inspect billing state with:

  • GET /api/v1/account/balance
  • GET /api/v1/account/ledger
  • GET /api/v1/account/holds
  • GET /api/v1/account/quotes
  • GET /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 debits
  • held_usd: funds currently reserved for active jobs
  • available_usd: spendable balance after holds
  • active_hold_count
  • open_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_estimate
  • summary.billing_admission
  • summary.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.

  1. Create a quote with POST /api/v1/account/quotes/topup and 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.
  2. Computalot returns 402 Payment Required.
  3. The response advertises the payment both ways: an x402 PAYMENT-REQUIRED header (mirrored in the body field payment_required) and an MPP WWW-Authenticate: Payment challenge (method evm, intent charge, decoded copy in the mpp block of the body). The top-up quote is in the body. The MPP challenge id is the quote id.
  4. Settle over either protocol:
    • x402: sign payment_required.accepts[0] and base64-encode the payment payload. Then retry POST /api/v1/account/quotes/:quote_id/pay/x402 with the payload in the PAYMENT-SIGNATURE header (bearer auth required). Success returns PAYMENT-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) with Authorization: Payment <credential> and no bearer token. Success returns a base64url JSON Payment-Receipt header.
  5. 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/init returns that shortfall quote, fund the account. Then retry POST /api/v1/projects/:name/init.
  • If POST /api/v1/jobs returns 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_effort
  • strict_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" } }
  • requirements are minimums. Computalot can place the work on larger machines.
  • profile: "cpu" or "gpu". CPU jobs can spill onto idle GPU capacity.
  • min_single_core_score floors 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, and max_wait_s return 422 until Computalot implements atomic reservation admission, expiry, and audit.
  • distribution.max_tasks_per_node also returns 422 until 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/init only 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 header

If 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.py

Projects 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.gz

After 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: true
  • init_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_estimate
  • summary.billing_admission
  • summary.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 (requires fan_out.by)
  • "weighted_avg" — the weighted average of a numeric field (set both value_field and weight_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/stream

The 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 job
  • GET /api/v1/jobs/watch?ids=id1,id2,... — one SSE connection for 2-100 jobs with terminal summaries, aggregate fields, and persistence flags
  • GET /api/v1/jobs/:id/tasks — per-task live_feedback, latest_progress, checkpoint, resume_state, runtime_s, health_status, plus the preserved last failed attempt while a retry is queued or running
  • GET /api/v1/results/:job_id — the canonical terminal result surface with per-task payload/result/output presence, completeness, and artifact IDs
  • GET /api/v1/jobs/:id/output — aggregated stdout/stderr that keeps the most recent failed attempt until the current attempt emits new diagnostics
  • GET /api/v1/jobs/:id — job-level feedback_summary and 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 payload

Or 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_sources for immutable remote inputs that Computalot must prepare before launch.
  • Use manifest cache_mounts for 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" with delivery = "mount". The worker then uses hf-mount.
  • If the runner downloads from Hugging Face itself, add a huggingface cache mount. Then HF_HOME and TRANSFORMERS_CACHE persist per worker. Ad hoc runtime downloads do not use hf-mount automatically.

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_ready

payload._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 payload small. Do not embed large data in JSON.
  • Use _artifacts.download for large inputs. Workers download and cache the files before launch.
  • The resolved paths are in payload._artifacts.local_paths. Single-file downloads also receive COMPUTALOT_ARTIFACT_<NAME> env vars.

Outputs:

  • Write checkpoints and files to $COMPUTALOT_ARTIFACT_DIR.
  • Use _artifacts.upload for 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, and result_filename through the authenticated relay path.

Operational:

  • Set timeout_s to more than the expected runtime, with margin.
  • Submit jobs after a push. Use POST /api/v1/projects/:name/init only to prepare currently available workers before a burst.
  • Write checkpoints and outputs to $COMPUTALOT_ARTIFACT_DIR. Use $COMPUTALOT_TASK_SCRATCH_DIR or $TMPDIR for 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:

  1. POST /api/v1/projects — register
  2. POST /api/v1/projects/:name/push — upload a tarball with the Dockerfile, computalot.project.json, and your code
  3. POST /api/v1/jobs — submit work against the published revision
  4. Optional: POST /api/v1/projects/:name/init — prepare currently available workers
  5. 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_jobs can already be true while ready_for_jobs is still false.
  • After a code change, push the new tarball. Use invalidate only to discard old prepared runtimes.

Project structure

my-project/ ├── Dockerfile ├── computalot.project.json ├── requirements.txt └── job.py

Dockerfile

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

  1. After setup changes, run one small smoke job before you submit a large batch
  2. Use manifest validation.executables and validation.files for declarative runtime checks
  3. Put dependencies and build steps in the Dockerfile. User uploads cannot declare runtime.init.commands, runtime.services, or validation.commands

Public project endpoints

  • POST /api/v1/projects
  • PUT /api/v1/projects/:name for metadata-only updates
  • POST /api/v1/projects/:name/push
  • POST /api/v1/projects/:name/init
  • POST /api/v1/projects/:name/invalidate
  • GET /api/v1/projects/:name/status
  • GET /api/v1/projects/:name/status/details
  • GET /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/details

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

  1. Computalot launches your runner_command with a task-specific payload
  2. Computalot writes the payload to a temp file. The path is in $COMPUTALOT_TASK_PAYLOAD
  3. The result file path is in $COMPUTALOT_TASK_RESULT
  4. Your script writes a JSON result to $COMPUTALOT_TASK_RESULT
  5. 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 payload field. 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: planningqueuedrunningcompleted | partial | failed | cancelled

  • Terminal states: completed, partial, failed, cancelled
  • Poll: GET /api/v1/jobs/:id every 2-5s until terminal
  • Stream: GET /api/v1/jobs/:id/stream for 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/cancel with {"reason": "..."}
  • Edit requirements: PATCH /api/v1/jobs/:id/requirements with {"requirements": {...}}. A value replaces the current value, and an explicit null removes 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 with 422. 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_retries from 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 in GET /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_requeued and attempt.lost events). 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 optional failure_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_s caps 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 cancelled
  • completed = 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 as current_node, provider IDs, runtime paths, and image refs and digests.

Result Presence

  • result_present and output_present in GET /api/v1/results/:job_id report whether durable result and output data exists.
  • summary.aggregate_result, summary.aggregate_aliases, and summary.completeness expose aggregation coverage.
  • result_quality and result_warnings are reserved and currently return null and [].
  • The API accepts and ignores legacy object-shaped result_schema metadata. Non-object values return 422. Runner exit status is authoritative: exit 0 completes, 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. terminal expands to the four terminal statuses.
  • since=2026-08-06T12:00:00Z — jobs with submitted_at at or after the cutoff. A date such as 2026-08-06 reads 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>/stream

The 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 done

Read 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

  1. Job error: GET /api/v1/jobs/:iderror and recommended_action
  2. Per-task details: GET /api/v1/jobs/:id/taskserror, output (up to 10 KB), and a structured failure result with failure_kind and exit_code. For long jobs, it also carries latest_progress, checkpoint, and resume_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.
  3. Live stream: GET /api/v1/jobs/:id/stream — SSE updates
  4. Timeline: GET /api/v1/jobs/:id/events — state change events
  5. Project readiness: GET /api/v1/projects/:name/status
  6. Diagnostics: GET /api/v1/projects/:name/status/details
  7. Billing state: GET /api/v1/account/balance, GET /api/v1/account/holds, GET /api/v1/account/ledger
SymptomCauseFix
402 Payment Required on top-up or shortfall flowaccount needs more creditspay the returned quote (x402 or MPP) and retry
project init rejected before setup startsavailable balance less than the funded floortop up to at least $5, then retry init
”No native library found”Missing system libraryCorrect the Dockerfile, push, invalidate, re-init
exit_code_1, useless errorTruncated errorRead the per-task output field (full 10 KB).
task looks blank while retryingthe current attempt did not emit anything yetRead 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 anythingworker or runtime preflight failed firstThe visible output / error can be platform stderr, not user stdout.
Cargo/Rust toolchain brokenComputalot worker faultWait for auto-recovery. Your code is not the cause
Tasks stuck in queuedCold start or capacity catch-upRead the project status and job diagnostics. The first job can wait while runtime preparation happens on demand
Project ready but tasks failThe Dockerfile misses dependencies, or declarative checks do not cover application behaviorCorrect the Dockerfile and run a small smoke job
401 / DB timeout after warmupCredentials errorRun 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/artifacts

Artifacts 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

MethodPathPurpose
GET/docsHuman docs landing page
GET/docs/pricingIndicative rates and worked cost examples
GET/api/v1/docsJSON docs index
GET/openapi.jsonOpenAPI 3.1 schema of the public API
GET/llms.txtCompact reference
GET/llms-full.txtFull reference with tutorials
GET/api/v1/docs/python-sdkPython SDK guide
GET/api/v1/docs/workflowsWorkflow patterns
POST/api/v1/auth/wallet/challengeCreate wallet auth challenge (no auth)
POST/api/v1/auth/wallet/verifyVerify wallet challenge and mint session token (no auth)
POST/api/v1/feedbackSubmit feedback (no auth)

Ops (operator-facing)

MethodPathPurpose
GET/healthLiveness probe (no auth, same body as /live)
GET/liveLiveness probe (no auth)
GET/readyReadiness probe (no auth, 503 until the controller core is up)
GET/metricsPrometheus metrics (admin auth, dedicated metrics token, or local request)

Account & Billing

MethodPathPurpose
GET/api/v1/account/balanceAccount credit summary
GET/api/v1/account/ledgerSettled ledger entries
GET/api/v1/account/holdsActive and historical holds
GET/api/v1/account/quotesFunding and shortfall quotes
GET/api/v1/account/quotes/:quote_idOne quote with its x402 requirements and MPP challenge
POST/api/v1/account/quotes/topupCreate a top-up quote (402 Payment Required, also settles MPP credentials)
POST/api/v1/account/quotes/:quote_id/pay/x402Settle a quote — x402 PAYMENT-SIGNATURE or MPP Authorization: Payment

Job API

MethodPathPurpose
POST/api/v1/jobsSubmit a job
POST/api/v1/jobs/batchSubmit up to 200 jobs
GET/api/v1/jobsList jobs (?status=&project=&tag=&limit=50&offset=0)
GET/api/v1/jobs/:idFull job state with feedback_summary and checkpoint summary
GET/api/v1/jobs/:id/outputStdout/stderr, with the preserved last-failed-attempt diagnostics during retries
GET/api/v1/jobs/:id/tasksPer-task details, errors, live feedback, checkpoint state, and preserved retry diagnostics
GET/api/v1/jobs/:id/eventsLifecycle events
GET/api/v1/jobs/:id/metricsAggregate metrics
GET/api/v1/jobs/:id/streamSSE stream for one job, with task live_feedback.output_tail deltas
GET/api/v1/jobs/watch?ids=a,b,cSSE stream for multiple jobs (max 100, with ping keepalives, metadata, and persistence flags)
GET/api/v1/projects/:name/streamSSE stream for all jobs in a project
PUT/api/v1/jobs/:id/cancelCancel a job
PATCH/api/v1/jobs/:id/requirementsEdit the requirements of all non-terminal tasks. A value replaces the current value, a null removes the key

Results & Artifacts

MethodPathPurpose
GET/api/v1/results/:job_idPer-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/resultsList 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/artifactsRelay upload artifact (raw body, max 2 GiB)
POST/api/v1/artifacts/externalRegister external URL artifact
GET/api/v1/artifactsList artifacts with the authoritative quota limit, used, and remaining bytes
GET/api/v1/artifacts/:idDownload artifact (authenticated requests stay on the public API URL, and metadata can expose a signed object-store URL)
GET/api/v1/artifacts/:id/metaArtifact metadata
DELETE/api/v1/artifacts/:idDelete when all referencing jobs are terminal. Active references return 409 artifact_in_use

Project API

MethodPathPurpose
POST/api/v1/projectsRegister project (name, remote_dir, optional env and setup_timeout_s)
GET/api/v1/projectsList projects
GET/api/v1/projects/:nameProject configuration + readiness status
PUT/api/v1/projects/:nameUpdate project metadata only (owner only)
DELETE/api/v1/projects/:nameDelete project (owner only)
POST/api/v1/projects/:name/pushUpload 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/pushRead the latest durable push/build status, image identity, structured error, and bounded log tail
GET/api/v1/projects/:name/pushes/:push_refRead one durable push/build status by reference
PUT/api/v1/projects/:name/cancel-queuedCancel queued/planning jobs for one project (optional tag filter)
POST/api/v1/projects/:name/initPrepare currently available workers (async)
POST/api/v1/projects/:name/invalidateForce re-init
GET/api/v1/projects/:name/statusProject readiness
GET/api/v1/projects/:name/status/detailsReadiness + diagnostics
GET/api/v1/projects/:name/kvList project-scoped shared state entries
PUT/api/v1/projects/:name/kv/:keyWrite project-scoped shared state value
GET/api/v1/projects/:name/kv/:keyRead project-scoped shared state value
DELETE/api/v1/projects/:name/kv/:keyDelete project-scoped shared state value
GET/api/v1/projects/:name/streamSSE stream for project jobs

Other

MethodPathPurpose
POST/api/v1/auth/registerDisabled self-service API-key issuance (403) with waitlist + beta-access guidance
GET/api/v1/presetsResource presets
Last updated on