Skip to Content

Synced from docs/llm-agent-reference.md in 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 started
  • 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-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 a WWW-Authenticate: Payment challenge (EVM charge; decoded copy in the body’s mpp block), quotes settle with Authorization: Payment <base64url credential> (EIP-3009), and success 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 served at /openapi.json; a pricing page with indicative market ranges lives at /docs/pricing; public endpoints answer HEAD like GET.
  • Job submission accepts preset (resource preset name from GET /api/v1/presets; explicit requirements win) and client_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, and GET /api/v1/artifacts reports authoritative used/remaining quota.
  • Worker process exit status is authoritative terminal truth. result_quality / result_warnings are reserved as null / []; non-empty result_schema submissions return 422.
  • Submission validates retries, dependencies, fan-out, resources, and account-owned artifact inputs before creating work or placing billing holds. max_retries accepts 0 through 10; depends_on accepts at most 50 job IDs.
  • User-upload projects require OCI + gVisor and 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. 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

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

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

  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 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 credits
  • GET /api/v1/account/ledger — transaction history
  • GET /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/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

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

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_estimate
  • summary.billing_admission
  • summary.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.

  1. POST /api/v1/account/quotes/topup with 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
  2. Computalot returns 402 Payment Required
  3. The response advertises the payment both ways: an x402 PAYMENT-REQUIRED header (mirrored in the body’s payment_required) and an MPP WWW-Authenticate: Payment challenge (method evm, intent charge; decoded copy in the body’s mpp block). 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], base64-encode the payment payload, and retry POST /api/v1/account/quotes/:quote_id/pay/x402 with it 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}), base64url-encode it, and POST the pay URL (or /topup) with Authorization: Payment <credential> — no bearer token needed. 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 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/init returns that shortfall quote, fund the account and retry POST /api/v1/projects/:name/init.
  • If POST /api/v1/jobs returns 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_effort
  • strict_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" } }
  • requirements are 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, and max_wait_s return 422 until atomic reservation admission, expiry, and audit are implemented.
  • distribution.max_tasks_per_node also 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/init only 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 header

If 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.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 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: 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 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 (requires fan_out.by)
  • "weighted_avg" — 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 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/stream

The 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 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 preserves 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/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 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 (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_sources for immutable remote inputs that should be prepared before launch
  • use manifest cache_mounts for writable caches your code creates at runtime
  • if the input lives on Hugging Face and should stay read-only, prefer data_sources[].source = "huggingface" with delivery = "mount" so the worker uses hf-mount
  • if the runner downloads from Hugging Face itself, add a huggingface cache mount so 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 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_ready

payload._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 payload small — do not embed large data in JSON.
  • Use _artifacts.download for large inputs. Workers download and cache before launch.
  • Resolved paths: payload._artifacts.local_paths. Single-file downloads also get 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; 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, and result_filename through the authenticated relay path.

Operational:

  • Set timeout_s above expected runtime with margin.
  • Submit jobs after push; use POST /api/v1/projects/:name/init only if you want to prepare currently available workers ahead of time.
  • Write checkpoints and outputs to $COMPUTALOT_ARTIFACT_DIR. Use $COMPUTALOT_TASK_SCRATCH_DIR or $TMPDIR for 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:

  1. POST /api/v1/projects — register
  2. POST /api/v1/projects/:name/push — upload tarball with Dockerfile + computalot.project.json + 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 published vs warm state
  • Project init is free but requires $5 available balance
  • Init is asynchronous
  • After a push, can_accept_new_jobs can already be true even while ready_for_jobs is still false
  • After a code change, push the new tarball; use invalidate only if you want 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. Run one small smoke job after setup changes before submitting 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

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

  1. Computalot launches your runner_command with task-specific payload
  2. Payload written to temp file; path in $COMPUTALOT_TASK_PAYLOAD
  3. Result file path in $COMPUTALOT_TASK_RESULT
  4. Your script writes JSON result to $COMPUTALOT_TASK_RESULT
  5. 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 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. Shell executables (bash, sh, zsh) are blocked.


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 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/cancel with {"reason": "..."}
  • 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/job fail; the billing hold covers the full requested attempt budget and attempt history stays in GET /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.lost events). 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_s is 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 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
  • strict_complete is 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 as current_node, provider IDs, runtime paths, and image refs/digests

Result Presence

  • result_present and output_present in GET /api/v1/results/:job_id report whether durable result/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 [].
  • Non-empty result_schema submissions 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.

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>/stream

Starts 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 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), structured failure result with failure_kind, exit_code, plus latest_progress, checkpoint, and resume_state for 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.
  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 below funded floortop up to at least $5, then retry init
”No native library found”Missing system libraryFix Dockerfile, push, invalidate, re-init
exit_code_1, useless errorTruncated errorCheck per-task output field (full 10 KB).
task looks blank while retryingcurrent attempt has not emitted anything yetCheck 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 anythingworker/runtime preflight failed firstThe visible output / error can be platform stderr rather than user stdout.
Cargo/Rust toolchain brokenComputalot worker issueWait for auto-recovery, not your code
Tasks stuck in queuedCold start or capacity catch-upCheck project status and job diagnostics; the first job may be waiting while runtime preparation happens on demand
Project ready but tasks failDockerfile missing deps or application behavior not covered by declarative checksFix the Dockerfile and run a small smoke job
401 / DB timeout after warmupCredentials issueVerify 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/artifacts

Artifacts 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

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 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 + its x402 requirements and MPP challenge
POST/api/v1/account/quotes/topupCreate top-up quote (402 Payment Required; also settles MPP credentials)
POST/api/v1/account/quotes/:quote_id/pay/x402Settle 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, including 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, including 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

Results & Artifacts

MethodPathPurpose
GET/api/v1/results/:job_idPer-task results plus metadata, aggregate_result, aggregate_aliases, completeness, and persistence flags; use job/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 → 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 authoritative quota limit/used/remaining bytes
GET/api/v1/artifacts/:idDownload artifact (authenticated requests stay on the public API URL; metadata may expose a signed object-store URL)
GET/api/v1/artifacts/:id/metaArtifact metadata
DELETE/api/v1/artifacts/:idDelete once 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, setup_timeout_s)
GET/api/v1/projectsList projects
GET/api/v1/projects/:nameProject config + 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; 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-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