{"result_guide":{"search":"Use GET /api/v1/results to search terminal jobs by job_id, ids, project, client_ref, tag, type, and user_id filters.","canonical_lookup":"Use job_id as the canonical identifier. GET /api/v1/results/:job_id is the default endpoint for one job's per-task results.","identifiers":{"job_id":"Canonical job identifier used by /jobs/:id and /results/:job_id.","tags":"Search labels only. Use /results?tag=... to filter.","client_ref":"Client-supplied search/grouping label. Not a result identifier.","artifact_id":"Artifact download identifier. Use GET /api/v1/artifacts/:id for files referenced by result payloads."},"live_updates":"Use GET /api/v1/jobs/:id/stream for one job, GET /api/v1/jobs/watch?ids=id1,id2 for many jobs, and GET /api/v1/projects/:name/stream for one project's full feed."},"billing":{"summary":"Computalot uses one account-level credit system for API-key callers and wallet-authenticated agents.","pricing":{"details":"/docs/pricing","authoritative_quote":"The summary.billing_estimate field in the submit response is the authoritative quote for the job (the rate, runtime source, markup, and minimum are itemized). Published ranges are indicative only.","indicative_ranges":"Observed live-market ranges (sampled 2026-07-12): CPU capacity ~$0.04-$0.45/hr by core count; GPU capacity ~$0.10-$0.50/hr for consumer classes (by VRAM tier); ~$2.40-$4.20 per GPU-hour for 80 GB datacenter class. See /docs/pricing for the class table and worked examples.","model":"Computalot meters compute per task-second at its rate for the requested resource class: the live market rate for that capacity plus a 35% service markup. The minimum billed duration per task-attempt is 60 seconds. Credits are prepaid. A refundable hold is reserved at submit, and it settles to the metered charge at terminal state. Computalot never charges for queue time. timeout_s caps the runtime cost of each task.","no_extra_charges":"There are no subscriptions, no idle charges, and no separate artifact storage or transfer fees today (the 100 GiB retained-artifact quota per account applies). Project init is free when the available balance is at least $5.00."},"account_endpoints":{"balance":"/api/v1/account/balance","quote":"/api/v1/account/quotes/:quote_id","holds":"/api/v1/account/holds","quotes":"/api/v1/account/quotes","ledger":"/api/v1/account/ledger"},"inspection":{"balance":"GET /api/v1/account/balance is the canonical balance snapshot: ledger_balance_usd, held_usd, available_usd, and open quote counts.","holds":"GET /api/v1/account/holds lists each active or historical hold. You can see which job admissions still reserve funds.","quotes":"GET /api/v1/account/quotes lists open top-up and shortfall quotes. Inspect pending funding actions there before you retry blocked work.","ledger":"GET /api/v1/account/ledger is the settled transaction history for credits, debits, and other posted account activity."},"mpp":"Computalot accepts MPP (Machine Payments Protocol, mpp.dev) on the same funding rail. Every quote 402 also carries a WWW-Authenticate: Payment challenge (method evm, intent charge). The challenge id is the quote id, and the `mpp` block in the 402 body includes the decoded request. To settle, POST the pay URL of the quote — or /api/v1/account/quotes/topup — with `Authorization: Payment <base64url credential>` that carries an EIP-3009 authorization (the same proof as the x402 exact scheme, no bearer token). Successful settlements return a base64url JSON Payment-Receipt response header.","shortfall_remedy":{"job_submit":"If POST /api/v1/jobs returns 402 Payment Required with a shortfall quote, inspect the same account surfaces and fund the account. Then retry the same submit request to POST /api/v1/jobs.","project_init":"If POST /api/v1/projects/:name/init returns 402 Payment Required with a shortfall quote because the funded floor is missing, fund the account. Then retry `POST /api/v1/projects/:name/init`.","shared_retry_rule":"A shortfall response blocks admission before work starts. Do not change the project or the payload first. When the funding gap is fixed, retry the same request."},"supported_access_paths":{"api_key":"Use an API key when the team provisioned access for you directly. It authenticates the same account surfaces: balance, holds, ledger, quotes, project init, and job submit.","wallet_session":"Use a wallet session for the self-serve wallet-auth + x402 flow. Any wallet signs in through challenge/verify. It can pay x402 quotes, and it reaches the same account billing truth."},"v1_policy":"Job execution reserves a bounded submit-time hold for the initial attempt plus the requested max_retries. The hold settles to actual internal cost-ledger totals at terminal completion. Infrastructure requeues do not consume the configured retry budget. Project init and artifact download stay free in v1, but Computalot still meters them internally. Project init currently requires a minimum funded balance floor of $5.","x402":"Create an x402 quote with POST /api/v1/account/quotes/topup. Then settle it with POST /api/v1/account/quotes/:quote_id/pay/x402 and the PAYMENT-SIGNATURE header. Submit-time or init-time insufficient-balance responses can also include a shortfall quote with PAYMENT-REQUIRED."},"jobs_vs_tasks":{"summary":"A job is a unit of work that you submit. Tasks are the parallel units that Computalot creates from your job. To run the same script on 1000 inputs, you can submit 1 job that fans out into 1000 tasks, or 1000 separate single-task jobs. This section helps you select one.","many_jobs":{"description":"Submit each unit of work as its own job. Each gets an independent ID, status, and lifecycle.","behaviors":["Each job has its own ID, status, and lifecycle. One failure does not affect the others.","Cancel, retry, or inspect any job independently.","You can watch each job independently or through the multi-job SSE endpoint.","Fine-grained depends_on: build DAGs where specific jobs depend on specific predecessors.","Per-job status in list views shows '743 completed, 12 running, 245 queued' at a glance.","Computalot schedules across jobs fairly by default. You can also set priority: high | normal | low to bias scheduling between otherwise comparable jobs."],"best_for":["Independent work items that must not affect each other on failure (for example unrelated customer requests)","Work where you need to cancel, retry, or inspect individual items independently","Pipelines with fine-grained DAG dependencies (job B depends on job A, job C depends on job B)","Work where each item needs an independently watched terminal state","Submissions that arrive over time rather than all at once"]},"one_job_many_tasks":{"description":"Use fan_out, sweep parameters, map_reduce split, or benchmark candidates. Computalot then expands a single job into many tasks.","behaviors":["One job ID to track. Poll or stream a single endpoint.","Cancel once to stop everything.","One SSE stream reports progress and terminal state for the entire job.","Job status reflects terminal task truth: 'completed' means all tasks succeeded, 'partial' means at least one task succeeded and at least one failed or was cancelled, and 'failed' means no task succeeded.","You read results per task with GET /api/v1/results/:job_id, but they belong to one job.","depends_on references this single job ID. Downstream work starts when every dependency reaches completed or partial. Failed or cancelled dependencies cancel blocked work.","Computalot aggregates results automatically for sweep (leaderboard), map_reduce (reduced values), and benchmark (statistics)."],"best_for":["Related work that must succeed or fail together (for example a grid search, a chunked simulation, or a benchmark comparison)","Work where you want a single aggregated result (sweep leaderboard, map_reduce aggregation, benchmark statistics)","Batch processing where individual items do not need independent lifecycle management","High-throughput pipelines. One job submission is faster than many"]},"rule_of_thumb":"If your 1000 inputs are one logical batch and you want one answer at the end, use one job with fan_out/split/parameters. If your 1000 inputs are independent requests that must succeed or fail on their own, use 1000 jobs. When in doubt, start with one job. It is simpler to manage and faster to submit."},"status_note":"Computalot is in public beta. Any wallet can authenticate (POST /api/v1/auth/wallet/challenge, sign, POST /api/v1/auth/wallet/verify) and fund the account with x402. No approval is necessary. Computalot issues API keys on request through the waitlist at /. Install the skill from /skill.md first. Use POST /api/v1/feedback (no auth) to report bugs and request features.","description":"Computalot is a distributed compute platform. You submit typed jobs, and you get structured JSON results. The platform supplies GPU and CPU workers.","platform_model":{"summary":"End users interact with Computalot through projects and jobs. Node provisioning, placement, runtime preparation, and mixed-hardware allocation are internal Computalot concerns.","placement_note":"Public API responses do not expose infrastructure identities. Computalot manages placement decisions, warm homes, and cluster topology internally.","user_visible_primitives":["projects define code and environment","jobs define work to run","requirements define minimum hardware per task","reservation.mode=best_effort is the only currently supported reservation shape","checkpointing defines whether structured-runner retries receive the latest checkpoint state"]},"documentation_updated_at":"2026-08-05","debugging_failures":{"summary":"When a job or task fails, read these endpoints.","common_issues":{"402 Payment Required":"Fund the account and retry the same request.","Setup fails":"Correct the Dockerfile, manifest, or runtime error. POST /invalidate. Then submit a job normally, or call POST /init to prepare currently available workers.","Tasks stuck in queued":"Read the project status. The revision can be published but still wait on runtime capacity or initialization.","exit_code_1 with no useful error":"Read the per-task output field for the full 10KB output."},"steps":["1. GET /api/v1/jobs/:id — read status, error, and recommended_action","2. GET /api/v1/jobs/:id/tasks — per-task error and output (up to 10KB)","3. GET /api/v1/jobs/:id/output — aggregated stdout/stderr","4. GET /api/v1/projects/:name/status — project readiness","5. GET /api/v1/projects/:name/status/details — diagnostics and recovery steps"]},"getting_started":{"overview":"Computalot runs jobs on managed CPU/GPU capacity. Authenticate with an API key or any wallet (challenge/verify, no approval necessary). The billing truth is on the account balance, holds, ledger, and quotes endpoints. If the account needs credits, fund it. Create a project environment. Then submit best-effort jobs with the resource minimums that you need. Computalot handles placement internally.","job_type_examples":[{"name":"structured_runner — single task or fan-out","example":{"request":{"path":"/api/v1/jobs","body":{"type":"structured_runner","payload":{"model":"gpt4","dataset":"test_v3"},"project":"my-proj","timeout_s":600,"runner_command":["python","evaluate.py"]},"method":"POST"},"note":"One task. For parallelism, add fan_out: {by: \"models\"} to split a list field into N tasks. Or use fan_out: {items: [%{...}, %{...}]} for one explicit payload object per task."}},{"name":"sweep — grid search","example":{"request":{"path":"/api/v1/jobs","body":{"type":"sweep","gpu_required":true,"project":"ml-training","parameters":{"batch_size":[32,64,128],"learning_rate":[0.001,0.01,0.1]},"timeout_s":3600,"runner_command":["python","train.py"],"fixed_payload":{"dataset":"cifar10","epochs":5},"rank_by":"accuracy","rank_order":"desc"},"method":"POST"},"note":"This request creates 9 tasks (3x3 grid). Each task receives one parameter combination in $COMPUTALOT_TASK_PAYLOAD. The API ranks results by accuracy."}},{"name":"map_reduce — chunked parallelism with aggregation","example":{"request":{"path":"/api/v1/jobs","body":{"type":"map_reduce","split":{"start":0,"total":10000,"chunks":50,"field":"seed"},"reduce":{"max_dd":"max","sharpe":"weighted_avg:sample_count","total_pnl":"sum"},"payload":{"strategy":"momentum"},"project":"monte-carlo","timeout_s":7200,"runner_command":["python","simulate.py"]},"method":"POST"},"note":"This request creates 50 tasks. Each task receives {seed_start, seed_count} in the payload. Computalot aggregates results with per-field operators. For explicit non-contiguous ranges, use split.ranges, for example %{field: \"seed\", ranges: [%{start: 860791000, count: 1000}, %{start: 200000000, count: 1000}] }."}},{"name":"benchmark — candidate comparison with replicas","example":{"request":{"path":"/api/v1/jobs","body":{"type":"benchmark","project":"my-proj","timeout_s":1800,"runner_command":["python","evaluate.py"],"rank_by":"score","candidates":{"baseline":{"model":"random"},"strategy_a":{"model":"gpt4","temperature":0.7},"strategy_b":{"model":"claude","temperature":0.5}},"replicas":3,"shared_payload":{"dataset":"test_set_v3","n_trials":100}},"method":"POST"},"note":"This request creates 9 tasks (3 candidates x 3 replicas). Each task receives the candidate configuration + _candidate + _replica in the payload. The result is a leaderboard with mean/std/min/max."}}],"quick_start":["1. Authenticate: use an API key, or POST /api/v1/auth/wallet/challenge with any wallet, sign the challenge, then POST /api/v1/auth/wallet/verify to get a session","2. Inspect account billing truth on GET /api/v1/account/balance, GET /api/v1/account/holds, GET /api/v1/account/ledger, and GET /api/v1/account/quotes","3. If your account needs credits, POST /api/v1/account/quotes/topup and settle it with POST /api/v1/account/quotes/:quote_id/pay/x402 — pay over x402 (PAYMENT-SIGNATURE header) or MPP (Authorization: Payment credential)","4. POST /api/v1/projects — register a project with name and remote_dir","5. Create a tarball with your code, Dockerfile, and computalot.project.json: tar czf code.tar.gz Dockerfile computalot.project.json script.py","6. POST /api/v1/projects/:name/push — upload the tarball (raw binary body). When the response is 202, poll the returned status_url","7. POST /api/v1/jobs — submit a best-effort job with optional requirements. If submit returns a shortfall quote, fund the account and retry the same submit request.","8. Optional: POST /api/v1/projects/:name/init to prepare currently available workers before a burst. If it returns a shortfall quote, fund the account and retry the same init request.","9. GET /api/v1/projects/:name/status — inspect whether the revision is only published or already ready_for_jobs","10. GET /api/v1/jobs/:id — poll until terminal status","11. GET /api/v1/results/:id — read per-task structured results (the default result surface), or GET /api/v1/results?project=my-proj&client_ref=batch_123 to find finished jobs","12. Use GET /api/v1/jobs/:id/stream, GET /api/v1/jobs/watch?ids=id1,id2, or GET /api/v1/projects/:name/stream for live progress instead of polling","13. GET /api/v1/artifacts — list your artifacts (includes artifacts from your jobs), then GET /api/v1/artifacts/:id to download files referenced by result artifact_ids"]},"docs":{"index":"/api/v1/docs","pricing":"/docs/pricing","python_sdk":"/api/v1/docs/python-sdk","workflows":"/api/v1/docs/workflows","changelog":"/docs/changelog","hosting":"/docs/hosting","llm":"/llms.txt","llm_full":"/llms-full.txt","openapi":"/openapi.json","skill":"/skill.md","web":"/docs"},"hosting":{"join":"On the machine, as root: `curl -fsSL https://computalot.com/computalot-host -o computalot-host && chmod +x computalot-host`. Then run `sudo ./computalot-host install --code cek_… [--cpus N --memory-gb N]`. The installer redeems the code (single-use, 24-hour expiry) over HTTPS for the configuration of the machine. You copy nothing to the machine by hand. Nothing ever connects in: the worker only dials out.","summary":"Bring your own hardware: a machine that you control joins the fleet as a permanent worker with a one-time enrollment code. The machine declares the share of itself that it lends. Every task runs inside a rootless gVisor sandbox. This is early access: the Computalot team issues the enrollment codes.","isolation":"Four boundaries protect the machine from the work that it runs. Each task runs in its own rootless gVisor sandbox (a userspace kernel — tasks never talk to the host kernel directly). Each task gets hard cpu, memory, and process caps inside the lend. Task traffic leaves through a private network namespace. The egress policy of that namespace rejects the machine's own addresses, loopback services, and the local network — only the internet is reachable. The worker itself runs as an unprivileged system user under a hardened systemd unit. Root is used once, at install time. No inbound ports, ever.","details":"/docs/hosting","lifecycle":"On the machine: computalot-host status | logs | stop | leave. `leave` removes everything that the installer created. Identity, the rate of the machine, and the kinds of work that it can receive are fixed controller-side at enrollment. A machine cannot rename itself or widen its own permissions.","resource_declaration":"The machine, not the controller, declares what it lends. --cpus and --memory-gb default to the whole machine. --gpus defaults to none. The lend is enforced outside the worker (systemd CPUQuota/MemoryMax or docker --cpus/--memory). The lend is exactly what the machine reports upward as its capacity.","status_note":"Early access: the hardware enrolled today is operator-run. Host earnings and payouts are still under construction (the hourly rate of each machine is recorded at enrollment). To join the program, use POST /api/v1/feedback (type feature_request) or the waitlist at /."},"feedback":{"types":["bug","feature_request","provisioning","job_type_request"],"endpoint":"POST /api/v1/feedback","note":"No auth is required. Report bugs, request features, and share ideas."},"recent_changes":{"items":["Bring-your-own-hardware early access: a machine that an owner controls joins the fleet with a one-time enrollment code (installer at GET /computalot-host, code redemption at POST /api/v1/fleet/enrollments/redeem). The machine declares the resources that it lends at install time. Every task runs inside a rootless gVisor sandbox. See /docs/hosting.","Controller-side OCI builds for projects are durable and asynchronous. POST /push returns 202 with push_ref and status_url. The prior revision stays active during the build. The build status shows published or failed state 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 on the funding rail. Quote 402 responses carry a WWW-Authenticate Payment challenge (EVM charge). Quotes settle with an Authorization: Payment credential that carries an EIP-3009 authorization. Successful settlements return a Payment-Receipt header.","Sealed recipes are no longer part of the public product surface. The launch model is projects + typed jobs. The previously documented /api/v1/recipes endpoints are retired from the public contract.","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 an 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 still return 409 artifact_in_use. Deletion releases account quota immediately. GET /api/v1/artifacts reports the used and remaining quota. Namespaced backing data has a default 24-hour GC grace period.","Worker process exit status is the terminal truth. result_quality and result_warnings are reserved as null and empty. The API accepts and ignores legacy object-shaped result_schema metadata.","Job admission validates retries, dependencies, fan-out, resources, and account-owned artifact inputs before it creates work or places billing holds.","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."],"details":"/docs/changelog","as_of":"2026-08-05"},"command_validation":{"allowed_executables":["python","python3","node","deno","bun","ruby","julia","Rscript","uv","pip","npm","npx","cargo","rustc"],"note":"runner_command must be an array with an allowed executable.","blocked_executables":"Computalot blocks bash, sh, zsh, and other shell executables.","example_invalid":["bash","-c","python train.py"],"example_valid":["python","train.py"]},"name":"computalot","resource_requirements":{"fields":{"cpu":"minimum CPU cores per task","profile":"\"cpu\" or \"gpu\". CPU jobs can spill onto idle GPU-capable capacity. GPU jobs require GPU-capable capacity.","gpu_count":"minimum GPU count per task","gpu_memory_mb":"minimum GPU memory in MB per task","memory_mb":"minimum RAM in MB per task","storage_gb":"minimum free disk in GB per task","min_single_core_score":"minimum relative single-core CPU speed per task, baseline 1.0. Modern desktop CPUs score near 1.1-1.4. Older server CPUs score near 0.3-0.9. Values above 3.0 return 422. Machines without reported CPU facts never match a scored task."},"summary":"Submit minimum resource needs with a job instead of a direct infrastructure target. Computalot can place the work on any larger matching runtime.","example":{"requirements":{"cpu":8,"profile":"gpu","gpu_count":1,"gpu_memory_mb":12288,"memory_mb":16384,"storage_gb":40}},"edit_after_submit":"PATCH /api/v1/jobs/:id/requirements edits these fields on a submitted job. A value replaces the current value. An explicit null removes the key. Only tasks that are not terminal change."},"reservations":{"fields":{"parallelism":"rejected with 422","mode":"only \"best_effort\" is currently accepted","guaranteed_for_s":"rejected with 422","max_wait_s":"rejected with 422"},"modes":{"best_effort":"default. The job is accepted, and it runs when matching capacity is available. An explicit mode is optional.","guaranteed":"returns 422 until atomic reservation admission and expiry are implemented"},"summary":"Guaranteed reservations are not available in execution-v3. The API rejects them rather than store them silently.","example":{"reservation":{"mode":"best_effort"}},"current_behavior":{"submit_failure":"POST /api/v1/jobs returns 422 for guaranteed or capacity-hold fields. The API therefore cannot accept an unenforced guarantee.","submit_success":"Omit reservation or pass {mode: best_effort} for normal queueing."}},"version":"v3","result_quality":{"summary":"result_quality and result_warnings are reserved response fields. They currently return null and an empty list. They do not affect terminal status.","custom_validation":"Custom result_schema validation is not implemented. The API accepts legacy object-shaped result_schema metadata for client compatibility and ignores it. Validate result contents in your runner. Use its exit status as the terminal truth."},"auth":{"type":"bearer","header":"Authorization: Bearer <token>","wallet_auth":"POST /api/v1/auth/wallet/challenge with your wallet address (chain: base, ethereum, or evm). Sign the returned message with the wallet. Then POST /api/v1/auth/wallet/verify to receive a short-lived fls_-prefixed session token (24h). Any well-formed wallet works. An allowlist entry is not necessary.","note":"Anonymous callers can use /health, /docs, /openapi.json, /llms.txt, /llms-full.txt, /api/v1/docs/*, POST /api/v1/feedback, POST /api/v1/auth/register, POST /api/v1/auth/wallet/challenge, and POST /api/v1/auth/wallet/verify. GET /metrics is for operators only: it requires admin auth, a dedicated metrics token, or a local request. Other endpoints require a bearer token: a wallet session (any wallet, through challenge/verify) or an API key. POST /api/v1/auth/register still returns 403 because self-service API-key issuance is disabled. Use wallet auth for self-serve access.","how_to_get_a_key":"Self-serve: authenticate any wallet through challenge/verify for an fls_ session token. Then fund with x402. No approval is necessary. Computalot issues flk_ API keys on request: join the waitlist at / or ask the team.","roles":{"member":"The default role. It creates and manages projects, jobs, and artifacts under your own account.","admin":"The operator role. It has full platform access through the admin API surface."},"scoping":"Endpoints are scoped by account ownership. API keys and wallet sessions both resolve to an account. You see the projects that you own and the jobs submitted under that account. Public responses do not expose infrastructure identities, because Computalot manages placement internally."},"project_manifest":{"docs":"/docs/projects/project-manifest","summary":"Container-based projects use a computalot.project.json manifest to define the runtime contract.","docs_absolute":"https://computalot.com/docs/projects/project-manifest","user_upload_policy":"Public user uploads require OCI + gVisor. The allowed declarative validation fields are executables and files. The API rejects runtime.init.commands, runtime.services, and validation.commands."},"job_lifecycle":{"output":"GET /api/v1/jobs/:id/output — aggregated stdout/stderr","stream":"GET /api/v1/jobs/:id/stream — SSE stream for one job","cancel":"PUT /api/v1/jobs/:id/cancel","statuses":["planning","queued","running","completed","partial","failed","cancelled"],"terminal":["completed","partial","failed","cancelled"],"watch":"GET /api/v1/jobs/watch?ids=... — SSE stream for multiple jobs (max 100)","results":"GET /api/v1/results/:job_id — per-task terminal results with artifact IDs and output-presence metadata","retention":"Terminal jobs stay queryable for 30 days. Artifact owners can delete when every referencing job is terminal. Deletion releases account quota immediately, and it schedules namespaced backing-object removal after the default 24-hour grace period. Historical job artifact links then become unavailable.","auto_retry":"Set max_retries on submission. A task that fails on its own (non-zero exit) auto-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 history of each attempt stays visible in GET /api/v1/jobs/:id/events.","infra_failures":"Infrastructure failures never consume your retry budget. Exit 137 (OOM, SIGKILL, preemption) and lost workers (the instance vanished mid-run) requeue the task automatically and uncapped, with the budget refunded. The events attempt.infra_requeued and attempt.lost record this. If a task dies repeatedly with exit 137, raise its memory expectations. The cause is not a code bug. Your runner can also declare an infra failure: write failure_class \"infra\" (and an optional failure_reason) in the result JSON and exit non-zero. Computalot then requeues the task with the budget refunded, 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.","polling":"GET /api/v1/jobs/:id — poll every 2-5s until terminal","terminal_outcomes":{"cancelled":"the job was cancelled before all work completed","completed":"every task completed successfully","failed":"no task completed successfully and execution ended in failure","partial":"at least one task completed and at least one task failed or was cancelled"},"edit_requirements":"PATCH /api/v1/jobs/:id/requirements — merge-patch the requirements of all non-terminal tasks. A value replaces the current value. An explicit null removes the key. Running attempts keep the old requirements. Retries use the new requirements."},"sdk":{"package":"computalot","python":{"install":"python3 -m pip install --user --break-system-packages https://computalot.com/docs/downloads/computalot-0.2.1-py3-none-any.whl","minimum_version":"3.10","quickstart":["from computalot import ComputalotClient","client = ComputalotClient(controller_url='https://computalot.com', token='YOUR_TOKEN')","docs = client.docs_index()","jobs = client.list_jobs(limit=5)"]}},"project_runtime":{"setup":"Include a Dockerfile and computalot.project.json in your tarball. OCI builds are staged durably. POST /push returns 202 with a push_ref and status_url. The current revision stays active until the image is published. Poll the status URL or GET /api/v1/projects/:name/push. Jobs trigger runtime preparation after publication. POST /init stays optional. See /docs/projects/project-manifest for the manifest schema.","summary":"Projects run as sandboxed OCI containers. Push a tarball with your code, Dockerfile, and computalot.project.json manifest. Computalot builds a container image and runs tasks in a sandboxed environment.","validation":"Use manifest validation.executables and validation.files for declarative runtime checks. Then run a small smoke job for imports, credentials, and connectivity.","restrictions":"User-upload projects require runtime.kind=oci and runtime.sandbox=gvisor. They cannot declare runtime.init.commands, runtime.services, or validation.commands. Put installs and build steps in the Dockerfile.","task_env":"Each task starts from the container image environment. It then loads project env files and applies meta.env overrides."},"controller_url":"https://computalot.com","recommended_endpoint":"Use https://computalot.com as the default Computalot API origin. Fetch https://computalot.com/api/v1/docs or https://computalot.com/llms.txt first. Agents and clients then anchor on the live public endpoint.","runner_protocol":{"progress":"For live progress updates, print COMPUTALOT_PROGRESS:{json} to stdout.","summary":"All job types use the runner protocol. Your script receives input and writes output through environment variables.","exit_code":"Exit code 0 means success. A non-zero exit code means failure.","example":"import json, os\npayload = json.load(open(os.environ['COMPUTALOT_TASK_PAYLOAD']))\nresult = {'score': 0.95}\njson.dump(result, open(os.environ['COMPUTALOT_TASK_RESULT'], 'w'))","env_vars":{"COMPUTALOT_ARTIFACT_DIR":"The directory for output files (models, checkpoints). Computalot uploads these files when the task completes.","COMPUTALOT_TASK_CACHE_DIR":"The project-scoped cache that ALL tasks on the same node share (it survives across tasks). Concurrency contract: concurrent tasks see the same directory. Do not stage a build in it at a fixed temporary name. Two tasks that build the same content race on the final rename (FileNotFoundError in pathlib .replace / shutil copy). Safe publish pattern: write to a unique per-task temp path in the SAME directory. Then rename atomically, and treat an already-present final file as success: `tmp = final.with_name(final.name + f'.tmp.{os.environ[\"COMPUTALOT_TASK_ID\"]}'); build(tmp); try: os.replace(tmp, final) except OSError: pass  # a sibling already published it` — or guard the whole build with fcntl.flock on a lockfile in the cache directory (advisory locks work on the cache mount). Before you build, look for the final file to skip redundant work.","COMPUTALOT_TASK_PAYLOAD":"The path to the JSON input file. Read this file.","COMPUTALOT_TASK_RESULT":"The path for your JSON output. Computalot reads this file after the process exits.","COMPUTALOT_TASK_SCRATCH_DIR":"A writable temporary directory for scratch files."}},"docs_absolute":{"index":"https://computalot.com/api/v1/docs","pricing":"https://computalot.com/docs/pricing","python_sdk":"https://computalot.com/api/v1/docs/python-sdk","workflows":"https://computalot.com/api/v1/docs/workflows","changelog":"https://computalot.com/docs/changelog","hosting":"https://computalot.com/docs/hosting","llm":"https://computalot.com/llms.txt","llm_full":"https://computalot.com/llms-full.txt","openapi":"https://computalot.com/openapi.json","skill":"https://computalot.com/skill.md","web":"https://computalot.com/docs"},"project_readiness":{"fields":{"init_state":"\"not_initialized\", \"published\", \"initializing\", \"ready\", \"refreshing\", or \"attention_required\"","content_hash":"current project upload hash. For image-backed OCI projects, Computalot still checks readiness against the prepared image_digest internally","availability_status":"\"cold\", \"warming\", \"ready\", \"degraded\", or \"mixed\" aggregate capacity summary for the active revision","requested_state":"controller-side init intent when present. It distinguishes a requested refresh from steady-state readiness","can_accept_new_jobs":"true when the latest project revision is published and can accept submissions. The first job can still pay a cold-start cost while Computalot prepares runtime capacity","failed_replicas":"count of replicas currently marked failed for the active revision","initializing_replicas":"count of replicas actively initializing now","last_ready_at":"ISO8601 timestamp of the most recent ready state, when available","project_ready":"true when Computalot currently considers the project runnable","ready_replicas":"count of currently ready replicas for the active revision","stale_replicas":"count of replicas that are stale relative to the active revision or unavailable worker set","total_replicas":"count of tracked replicas contributing to readiness","ready_for_jobs":"true when Computalot considers the active revision platform-ready to admit work without a wait for runtime preparation. This is not proof that your application imports, credentials, or wrapped subprocesses are correct, unless validation covers them","next_action":"human-readable guidance for what to do next","status_message":"human-readable summary of what Computalot is doing now","progress_phase":"\"queued\", \"running\", \"failed\", \"refreshing\", \"refresh_pending\", \"ready\", or \"idle\". Clients can then tell waiting-for-capacity from active setup and terminal failure","latest_activity_at":"most recent sanitized init activity timestamp across diagnostics, when available","latest_failure_at":"most recent sanitized failed-attempt timestamp, when available","pending_init":"true when Computalot currently has project init work queued or in flight","latest_issue":"optional sanitized setup error summary when attention is required"},"summary":"Project readiness is active-revision truth that Computalot manages, not machine counts or per-machine deployment state.","diagnostics":"GET /api/v1/projects/:name/status/details returns the same active-revision readiness summary plus sanitized diagnostics entries for setup or refresh errors: id, status, phase, message, log_tail, content_hash, inserted_at, updated_at, initialized_at, and recommended_action. Use the top-level status for runnable truth. Use status/details when you need recovery guidance."},"heavy_job_guidance":{"summary":"For GB-scale datasets, large checkpoints, and long training runs, treat the job payload as control-plane data only.","outputs":["Write checkpoints and other task-produced files under $COMPUTALOT_ARTIFACT_DIR.","Use _artifacts.upload for named worker uploads. Declared paths must be relative to $COMPUTALOT_ARTIFACT_DIR. The upload relays through the authenticated controller endpoint.","If a structured JSON result is too large to store inline, Computalot spills it to an artifact and returns result_spilled, result_artifact_id, and result_filename."],"inputs":["Do not embed large datasets, archives, or model weights in payload JSON. Submit small metadata in payload and move large inputs through artifacts.","Use _artifacts.download for large inputs. Workers download and cache these files before launch.","For reusable remote datasets or model weights, declare manifest data_sources. The worker then prepares them before launch instead of an ad hoc download inside the runner.","For Hugging Face-hosted immutable inputs, declare a manifest data_source with source=huggingface. Use delivery=mount when you want worker-managed hf-mount rather than a runner-side snapshot_download call.","_artifacts.download accepts concrete artifact IDs only: a string ID, %{id: \"artifact-id\"}, or a non-empty list of those forms. Before you submit the downstream job, resolve upstream artifact IDs from GET /api/v1/results/:job_id.","payload._shared.resolve is not supported. Read project shared state through GET /api/v1/projects/:name/kv before submission. Then put the resolved value directly in the job payload.","Project-scoped shared state lives behind GET/PUT/DELETE /api/v1/projects/:name/kv/:key. Computalot does not inject it automatically at dispatch.","Computalot injects resolved artifact paths into payload._artifacts.local_paths. Single-file entries also receive COMPUTALOT_ARTIFACT_<NAME> env vars."],"operational_defaults":["Use external or object-storage artifacts for multi-GB datasets and model bundles.","Declare cache_mounts for writable package and model caches that your code populates at runtime. For Hugging Face or Transformers downloads, use a huggingface cache mount. Then HF_HOME and TRANSFORMERS_CACHE persist per worker.","hf-mount applies only to manifest-declared Hugging Face data_sources. If your runner downloads from Hugging Face directly, it will not use hf-mount unless you route that data through data_sources or a declared cache mount.","Enable checkpointing.resume_from_latest for long jobs, and emit periodic durable checkpoints. Retries can then resume instead of a restart from zero.","Set timeout_s with headroom over the expected task runtime after worker start. Internal controller policy manages queue time.","Start project setup before the run. Then initialization does not consume the first training attempt."]},"status":"public_beta","choosing_a_job_type":[{"when":"You have a script, a JSON payload, and want 1 task (or simple fan-out over a list field).","use":"structured_runner"},{"when":"You want to run every combination of parameters and rank the results. Examples: grid search, hyperparameter tuning.","use":"sweep"},{"when":"You want to split a numeric range into chunks, process the chunks in parallel, and aggregate with operators (sum, mean, max, and more).","use":"map_reduce"},{"when":"You want to compare two or more named candidates, with replicas for statistical significance.","use":"benchmark"}],"project_lifecycle":{"setup":"Include a Dockerfile and computalot.project.json manifest. Controller-side OCI builds return 202 after durable staging. Poll status_url until the build is published or failed. The prior revision stays active during the build. Jobs trigger runtime preparation on demand after publication. POST /init is optional. See /docs/projects/project-manifest for the manifest schema.","summary":"Register -> push a tarball (Dockerfile + computalot.project.json + your code) -> submit jobs. Optional: call init to prepare currently available workers before a burst.","debugging_init":"Use GET /api/v1/projects/:name/status for readiness. Then use GET /api/v1/projects/:name/status/details for diagnostics and recovery steps.","update_flow":"Push the new tarball -> optional POST /invalidate to discard old prepared runtimes -> submit jobs normally. The first job on the new revision can cold-start while runtime preparation catches up."},"api":{"auth":[{"path":"/api/v1/auth/register","response":"403: {error, recommended_action, details}. The details.wallet_auth block describes the current wallet-auth policy; details.waitlist points at the landing-page waitlist for API keys.","body":{"name":"string (ignored while disabled)","email":"string (ignored while disabled)"},"method":"POST","purpose":"Self-service API-key issuance is disabled. No auth is required, but the endpoint returns 403 with current access guidance (wallet auth, the landing-page waitlist, or admin-issued keys)."},{"path":"/api/v1/auth/wallet/challenge","response":"201: {challenge: {id, chain, wallet_address, nonce, message, status, expires_at}}. 422 for malformed chain/address input.","body":{"chain":"string (default base)","wallet_address":"string"},"method":"POST","purpose":"Create a wallet auth challenge for an autonomous agent wallet. No auth is required. The API accepts any well-formed wallet."},{"path":"/api/v1/auth/wallet/verify","response":"201: {account, wallet, session, token}. 401 for invalid signatures or wallet/chain mismatch. 409 for reused challenges. 410 for expired challenges. 422 for malformed verify input. token is an fls_-prefixed bearer token.","body":{"signature":"string","wallet_address":"string","challenge_id":"string"},"method":"POST","purpose":"Verify a signed wallet challenge and mint a short-lived session token. No auth is required. The first verify for a new wallet also creates the account. This is the self-serve onboarding path."}],"ops":[{"path":"/health","method":"GET","purpose":"Liveness probe (no auth). Returns {\"status\":\"ok\",\"app\":\"computalot_api\"}."},{"path":"/live","method":"GET","purpose":"Liveness probe (no auth). Same as /health."},{"path":"/ready","method":"GET","purpose":"Readiness probe (no auth). 200 with {checks: {repo, api_supervisor, controller_core}} when the controller core is up; 503 otherwise."},{"path":"/metrics","method":"GET","purpose":"Prometheus metrics (operator-gated). Requires a local request, admin auth, or a dedicated metrics token."}],"results":[{"path":"/api/v1/results/:job_id","response":"200: {job_id, project, status, client_ref, tags, meta, variant?, artifact_ids, links, summary, aggregate_result, aggregate_aliases, completeness, result_persisted, output_persisted, results: [{task_id, status, payload, result, artifact_ids, result_artifact_id?, result_present, result_quality, result_warnings, output, output_present, error, project_content_hash?, started_at, completed_at}], count, result_count, output_count}","method":"GET","note":"This is the default way to read task outcomes. The 'result' field contains the JSON that your runner wrote to $COMPUTALOT_TASK_RESULT. The worker process exit status is authoritative: exit 0 completes the task and non-zero fails it, regardless of fields inside the JSON result. result_quality and result_warnings are reserved and currently null/empty. 'summary' includes aggregate_result, aggregate_aliases, completeness, task_outcome_counts, and the result_persisted/output_persisted flags. Weighted fan-out jobs also expose completeness coverage fields such as weight_field, expected_weight, completed_weight, and pending_weight. Chunk fan-out payload aliases such as chunk_index, chunk_count, and seed_range stay visible per task. 'artifact_ids' lists files that the task produced or large spilled result blobs. Download those with GET /api/v1/artifacts/:id, or list follow-up files through GET /api/v1/artifacts. 'client_ref' and 'tags' help you search, but job_id stays the canonical lookup key. For live retry-loop diagnostics, use GET /api/v1/jobs/:id/output and GET /api/v1/jobs/:id/tasks, because they keep the latest failed-attempt output and error even before the next attempt finishes. Each result entry also carries the attempts array with per-attempt execution metadata: pseudonymous worker handles, timing, retry decisions, and worker resource profiles. Placement infrastructure is not part of the public result surface, so node names, provider IDs, raw runtime paths, and image refs/digests stay redacted.","purpose":"Get per-task completion records for one job, with structured result/output presence flags, artifact IDs, and follow-up links. Reads from PG — available immediately after task completion."},{"path":"/api/v1/results","response":"200: {results, count, limit, offset, applied_filters, result_guide, group_by?, groups?}. 422 when limit/offset/status/since are malformed (non-integer limit/offset, unknown status value, non-ISO-8601 since).","method":"GET","query_params":"?limit=20&offset=0&job_id=job_...&ids=job_a,job_b&project=my-proj&client_ref=batch_123&tag=experiment_alpha&since=2026-08-06T00:00:00Z&user_id=42&group_by=project&include_tasks=false","note":"Defaults to terminal statuses (completed, partial, failed, cancelled) rather than only completed jobs. status accepts one value or a comma-separated list. since filters to jobs with submitted_at at or after an ISO 8601 timestamp or date (dates read as midnight UTC) — pass your last sync time to fetch only newly finished work. Pagination uses limit (1-200, default 20) and offset (>= 0, default 0). Malformed values return 422 with a specific error message, and the response echoes both back. The response includes applied_filters, a result-guide block, and per-job links. Users can pivot directly to /jobs/:id, /jobs/:id/tasks, /jobs/:id/stream, or /results/:job_id.","purpose":"List terminal jobs in result-oriented form so you can find finished work before drilling into GET /api/v1/results/:job_id."}],"account":[{"path":"/api/v1/account/balance","method":"GET","purpose":"Get account credit summary: ledger balance, held funds, available funds, and open quote count. This is the canonical balance snapshot."},{"path":"/api/v1/account/ledger","method":"GET","purpose":"List settled credit ledger entries for the current account"},{"path":"/api/v1/account/holds","method":"GET","purpose":"List active and historical holds for the current account"},{"path":"/api/v1/account/quotes","method":"GET","purpose":"List funding and shortfall quotes for the current account"},{"path":"/api/v1/account/quotes/:quote_id","method":"GET","purpose":"Get one quote by id (account-scoped), including its x402 payment requirements in attrs.x402_payment_required. Returns 404 for unknown ids or quotes owned by another account."},{"path":"/api/v1/account/quotes/topup","method":"POST","purpose":"Create a funding quote. The endpoint returns 402 Payment Required and advertises both rails: x402 in the PAYMENT-REQUIRED header and body, and MPP in the WWW-Authenticate: Payment header plus the `mpp` block of the body. It returns 422 when amount_usd is malformed, not positive, or more than the $10,000 per-top-up cap. A repeat POST to this path with an MPP `Authorization: Payment` credential settles the quote that the credential references instead of a new quote."},{"path":"/api/v1/account/quotes/:quote_id/pay/x402","method":"POST","purpose":"Settle a quote and credit the account on success. There are two credential carriers: x402 through the PAYMENT-SIGNATURE header (bearer auth required), or MPP through `Authorization: Payment <base64url credential>` (EIP-3009 authorization, no bearer token — the credential plus the unguessable quote id authorize the settlement, and success adds a Payment-Receipt header). The first settlement returns 201. Replay-safe repeats return 200 with replay=true. Malformed credentials return 422. Settlement failures return 402 with machine-readable payment details."}],"artifacts":[{"path":"/api/v1/artifacts","response":"201: {id, sha256, size, source: local}. 507 with code artifact_quota_exceeded when retained account storage would exceed the default 100 GiB quota.","headers":"X-Artifact-Filename: name.ext, X-Artifact-Job-Id: job_external_id (optional, links artifact to job for access control)","method":"POST","note":"The body streams directly to disk with no memory buffering. Computalot packages all files in COMPUTALOT_ARTIFACT_DIR and uploads them as a single archive.","purpose":"Upload artifact through the authenticated controller relay (streaming, max 2 GiB). Computalot also auto-uploads task artifacts as tar.gz."},{"path":"/api/v1/artifacts/external","body":{"filename":"string","url":"string","sha256":"optional string"},"method":"POST","purpose":"Register external URL (no upload)"},{"path":"/api/v1/artifacts","method":"GET","purpose":"List your artifacts (this includes artifacts from your jobs, even when Computalot uploaded them automatically at task completion)"},{"path":"/api/v1/artifacts/:id","method":"GET","purpose":"Download artifact binary (access: own artifacts + artifacts from own jobs). Authenticated GET /api/v1/artifacts/:id requests stream bytes through the controller. Artifact metadata can expose a signed object-store URL when a client wants object-store details."},{"path":"/api/v1/artifacts/:id/meta","method":"GET","purpose":"Get artifact metadata"},{"path":"/api/v1/artifacts/:id","method":"DELETE","purpose":"Reference-safe deletion. 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, deletion hides metadata and releases account quota immediately. It then schedules namespaced backing-object GC after the default 24-hour grace period."}],"jobs":[{"path":"/api/v1/jobs","response":"201: job object with id, status, type, requirements, reservation, checkpointing, and summary.billing_estimate/billing_admission. When funded, summary.billing_hold is also present. 402: PAYMENT-REQUIRED shortfall quote when the account cannot admit the request yet. Fund the account. Then retry the same submit request. 422: invalid dependency, retry, fan-out, result-schema, callback, or resource contract. Invalid artifact ownership is rejected before admission. Public responses do not include placement infrastructure.","body":{"priority":"optional string — high | normal | low. Default normal. Biases scheduling between otherwise comparable jobs without exposing infrastructure details.","type":"structured_runner | sweep | map_reduce | benchmark","split":"object — {field, start, total, chunks} (map_reduce only)","state":"reserved — omit this field. Use the project KV endpoints explicitly before submission for small coordination values.","reduce":"map of field -> operator (map_reduce only). Operators: sum, mean, max, min, weighted_avg:<weight_field>, concat, count, collect.","tags":"array of strings — labels for grouping/filtering (max 20). Query with GET /jobs?tag=sweep_72.","gpu_required":"bool (default false)","payload":"object — task input (structured_runner, map_reduce). Written to $COMPUTALOT_TASK_PAYLOAD. payload._artifacts.download accepts concrete account-owned artifact IDs only; ownership and retained references are checked atomically before work or a billing hold is created.","fan_out":"object — {by: \"field\"}, {items: [%{...}, ...]}, or {chunks: N, total: N} (structured_runner only). These shapes are mutually exclusive: mixing `by`, `items`, or `chunks` + `total` returns 422. `batch_size` / `batch_per_task` groups multiple fan-out items into one dispatched task while preserving batch metadata in payload._batch.","project":"string — registered project name","tasks":"not accepted on the public API. Use fan_out.items for per-task payloads, or submit separate jobs. This rule prevents per-task command and routing overrides from bypassing the validated runner contract.","parameters":"map of param_name -> [values] (sweep only). Cartesian product, max 1000 combos.","max_retries":"int 0-10 (default 0) — the upper bound for workload retries after the initial attempt. The cause-aware policy retries only when the next attempt can improve the outcome. Infrastructure loss and runner crashes requeue without this budget. Deterministic configuration failures fail fast. Timeouts retry only from a durable checkpoint. Repeated matching failures open a job-scoped retry circuit. The billing hold covers every requested workload attempt.","client_ref":"optional string (max 255 bytes) — client-supplied grouping/search label. Not an identifier; filter terminal jobs with GET /api/v1/results?client_ref=...","depends_on":"array of a maximum of 50 account-accessible job IDs — DAG dependency. Dispatch waits until every dependency is completed or partial. Failed or cancelled dependencies cancel the blocked job.","checkpointing":"object (optional, structured_runner only) — {enabled, resume_from_latest, payload_key}. When enabled, tasks can emit progress and result checkpoint maps under `checkpoint`. Computalot durably publishes artifact-backed checkpoints when an `artifact_id` is present or a checkpoint path is publishable. Retries recover that state into the next task payload.","requirements":"object (optional) — minimum cpu, memory_mb, storage_gb, gpu_count, gpu_memory_mb, min_single_core_score, profile for each task","timeout_s":"int (default 3600) — the runtime cap for each task after a worker starts it. Queue time is internal controller policy","runner_command":"array — for example [\"python\", \"script.py\"]. Required.","reservation":"object (optional) — only {mode: best_effort} is supported. guaranteed, parallelism, guaranteed_for_s, and max_wait_s return 422.","callback_url":"reserved — non-empty values return 422 until durable webhook delivery is available. Use job SSE/watch or polling","preset":"optional string — a resource preset name from GET /api/v1/presets (for example gpu_batch). It seeds default requirements and the billing class. Explicit requirements fields win over preset values. Unknown names return 422.","runtime_hint_s":"optional int — expected runtime hint used by hold estimation. timeout_s remains the runtime hard ceiling after worker start.","reliability_mode":"optional string — best_effort | strict_complete. Use strict_complete for research-sensitive fan-out work.","merge_strategy":"collect | keyed | weighted_avg (structured_runner only)","result_schema":"reserved — omit this field. Non-empty values return 422 until authoritative result-schema validation is implemented.","fixed_payload":"object merged into every task (sweep only)","rank_by":"string — result field to rank/sort by (sweep, benchmark)","rank_order":"asc | desc (default desc) (sweep, benchmark)","candidates":"map of name -> config (benchmark only). Min 2 candidates.","replicas":"int (default 1) — runs per candidate (benchmark only)","shared_payload":"object merged into every task (benchmark only)"},"method":"POST","purpose":"Submit a job"},{"path":"/api/v1/jobs","response":"200: {jobs, total, count, limit, offset}. Each job includes: payload (extracted from request), user_id, has_error (bool), has_output (bool), error_snippet (string|null, ~120 char extract). Heavy fields (request, output, summary, error) are stripped — use GET /jobs/:id for full data. 422 when status, since, ids, limit, or offset are malformed.","method":"GET","query_params":"?status=queued,running&project=my-proj&type=structured_runner&tag=sweep_72&since=2026-08-06T00:00:00Z&limit=50&offset=0","note":"status accepts one value or a comma-separated list (queued, planning, running, completed, partial, failed, cancelled, terminal — terminal expands to the four terminal statuses). since filters to jobs with submitted_at at or after an ISO 8601 timestamp or date (dates read as midnight UTC; zone-less timestamps read as UTC) — pass your last sync time to fetch only new jobs. tag matches jobs whose tags array contains the exact value. ids=job_a,job_b fetches a specific set in one call.","purpose":"List jobs (your projects + jobs you submitted)"},{"path":"/api/v1/jobs/:id","method":"GET","purpose":"Get full job state. Access: own projects and own jobs. Poll until status is terminal. Includes requirements, reservation, checkpointing, feedback_summary, and checkpoint summary when enabled. It does not expose placement infrastructure."},{"path":"/api/v1/jobs/:id/output","response":"200: {output, error}","method":"GET","note":"If a platform/runtime failure happens before the user process starts, output/error can contain preflight stderr from Computalot rather than user stdout/stderr.","purpose":"Read stdout/stderr output. During auto-retry, this endpoint keeps the diagnostics of the most recent failed attempt until the current attempt emits its own output."},{"path":"/api/v1/jobs/:id/tasks","response":"200: {tasks: [...], count}","method":"GET","note":"Each task includes: status, result_present, output_present, output (full stdout, up to 10KB), error (last ~1000 chars on failure), result (structured JSON), result_quality, result_warnings, retry_summary, live_feedback, latest_progress, checkpoint, resume_state, runtime_s, stale_for_s, health_status, started_at, completed_at. retry_summary separates workload retries, infrastructure retries, runner-declared infra retries, checkpoint resumes, unclassified legacy retries, and policy-suppressed retries. It includes the last failure class and decision when available. During auto-retry, queued and running tasks can continue to show the output and error of the previous failed attempt until the current attempt emits its own diagnostics. Checkpoint state can include durable publication fields such as artifact_id, artifact_source, publish_status, and published_at. Resume payloads can include artifact-backed checkpoint download metadata. For failed tasks, result can be a machine-readable failure payload with fields such as failure_kind, exit_code, command, cwd, and combined_output. Each task also includes an attempts array: one entry per execution attempt, in attempt order. Each attempt entry gives attempt, 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 handle stays the same when the same machine runs another attempt, so you can see placement spread across retries. resource_profile shows the hardware of that worker (cpu_model, cpu_clock_ghz, single_core_score, cpu_count, memory_total_mb, gpu_count). Public task responses keep the submitted task payload contract, but they do not expose node names, current_node, provider IDs, raw runtime paths, or image refs/digests. Worker identity appears only as the pseudonymous handle.","purpose":"List tasks with individual statuses, result/output presence flags, output, and errors"},{"path":"/api/v1/jobs/:id/events","method":"GET","query_params":"?limit=200","purpose":"Lifecycle events (state changes, progress, errors)"},{"path":"/api/v1/jobs/:id/stream","method":"GET","purpose":"Authenticated SSE stream for live job feedback. Emits snapshot, job, task, event, done, and timeout frames."},{"path":"/api/v1/jobs/watch","query":"ids=id1,id2,... (comma-separated, max 100)","method":"GET","note":"One connection is more efficient than one /stream per job. It fits batch submissions where you track 2-100 jobs. Idle periods emit ping keepalives. Snapshot and terminal job frames include client_ref, tags, meta, and variant, plus the same public summary fields that GET /api/v1/results/:job_id exposes: aggregate_result, aggregate_aliases such as avg_edge when available, completeness coverage, and the result_persisted/output_persisted flags.","purpose":"Watch multiple jobs via a single SSE connection (max 100 IDs). Emits snapshot, per-job deltas, and done when all are terminal."},{"path":"/api/v1/jobs/:id/metrics","response":"200: {total_tasks, avg_runtime_s, throughput_per_min, retry_count, retry_summary}. retry_count counts workload and legacy-unclassified retries for compatibility; retry_summary separately reports total_retries, workload_retries, infrastructure_retries, self_reported_infra_retries, checkpoint_resumes, unclassified_retries, and suppressed_retries.","method":"GET","purpose":"Aggregate runtime and cause-aware retry metrics"},{"path":"/api/v1/jobs/:id/cancel","body":{"reason":"string"},"method":"PUT","purpose":"Cancel a job. Kills running tasks and releases reserved or inflight capacity."},{"path":"/api/v1/jobs/:id/requirements","response":"200: {status: \"updated\", job_id, updated_tasks, requirements_set, requirements_removed}. 404 when the job does not exist or is not yours. 409 when the job is already terminal. 422 when the merged requirements of any task are invalid — no task changes.","body":{"requirements":{"memory_mb":null,"min_single_core_score":0.8}},"method":"PATCH","note":"Merge semantics: a value replaces the current value, and an explicit null removes the key. Keys and values use the same validation as submit-time requirements (see resource_requirements). Queued tasks match against the new requirements on the next lease poll. Running attempts finish under the old requirements. Later retries use the new requirements. Computalot does not re-estimate the admission-time balance hold. Settlement always bills the actual metered use.","purpose":"Edit the resource requirements of a submitted job without cancel-and-resubmit. The patch applies to every task of the job that is not terminal."}],"feedback":[{"path":"/api/v1/feedback","body":{"type":"bug | feature_request | provisioning | job_type_request","description":"string","title":"string"},"method":"POST","purpose":"Submit feedback (no auth required)"}],"projects":[{"path":"/api/v1/projects","response":"201: project object with name, remote_dir, env, setup_timeout_s, runtime_kind, image_ref, image_digest, manifest, cache_policy, runtime_version, content_hash, created_at, updated_at","body":{"env":"optional object of project-level runtime env vars merged after env files and before meta.env","name":"string (1-64 chars, a-z0-9_-)","remote_dir":"string (absolute path where Computalot prepares the project environment)","image_digest":"optional OCI image digest","runtime_kind":"optional string, must be oci for public execution","image_ref":"optional OCI image reference","manifest":"optional object for runtime contract metadata","cache_policy":"optional object for explicit cache policy","runtime_version":"optional positive integer runtime contract version","setup_timeout_s":"optional int > 0. Overrides the default 600s project setup timeout."},"method":"POST","purpose":"Register a new project"},{"path":"/api/v1/projects","response":"200: {count, projects: [...]}","method":"GET","purpose":"List your projects"},{"path":"/api/v1/projects/:name","response":"200: project object with runtime metadata plus init_status {project_ready, can_accept_new_jobs, ready_for_jobs, init_state, availability_status, progress_phase, pending_init, requested_state?, content_hash, status_message, next_action, latest_issue?, last_ready_at, latest_activity_at?, latest_failure_at?, ready_replicas, initializing_replicas, failed_replicas, stale_replicas, total_replicas, queue_depth, queued_tasks, active_jobs, active_tasks}","method":"GET","purpose":"Get project config plus Computalot-managed readiness status"},{"path":"/api/v1/projects/:name","response":"200: updated project object. 404 if not found. 422 if you send tarball or code fields. Use POST /api/v1/projects/:name/push for code updates.","body":{"env":"optional object of project-level runtime env vars","remote_dir":"optional string","image_digest":"optional OCI image digest","runtime_kind":"optional string, must be oci for public execution","image_ref":"optional OCI image reference","manifest":"optional object for runtime contract metadata","cache_policy":"optional object for explicit cache policy","runtime_version":"optional positive integer runtime contract version","setup_timeout_s":"optional int > 0"},"method":"PUT","purpose":"Update project metadata only (owner only)"},{"path":"/api/v1/projects/:name","method":"DELETE","note":"Blocked while the project has active (queued/running) jobs. Cancel them first.","purpose":"Delete project + tarball (owner only)"},{"path":"/api/v1/projects/:name/push","response":"202 for controller-side OCI builds: {push_ref, status, status_url, content_hash, size_bytes, terminal, next_action, tarball_diff?}. 200 for revisions that do not require a controller build. 409 includes active_push when another build is queued/running. 422: {error, details}.","method":"POST","note":"Raw gzip binary body (not multipart/form-data). Max 100MB. Include the Dockerfile and computalot.project.json in your tarball. Controller-side OCI builds return 202 after the tarball and build record are durable. The prior published revision stays active while the build runs. Returns 400 if the body is not valid gzip, 409 if another push or an active initialization is in progress, and 422 for an invalid tarball or manifest.","purpose":"Upload code tarball (owner only)"},{"path":"/api/v1/projects/:name/push","response":"200: {push_ref, status, status_url, content_hash, image_ref, image_digest, error, log_tail, terminal, timestamps, next_action}. Status is queued, building, published, or failed.","method":"GET","purpose":"Inspect the latest durable project push (owner only)"},{"path":"/api/v1/projects/:name/pushes/:push_ref","response":"200: the same durable push status shape, including bounded build log_tail on success or failure.","method":"GET","purpose":"Inspect one durable project push (owner only)"},{"path":"/api/v1/projects/:name/init","response":"200: {status, ready_for_jobs, status_message, init_status}. 402: PAYMENT-REQUIRED shortfall quote when available balance is below the init funded floor; after funding, retry the same POST /api/v1/projects/:name/init.","body":{"max_nodes":"optional int"},"method":"POST","note":"Async and optional. Poll GET /projects/:name/status for progress. Push already builds the OCI image. This endpoint only prepares the runtime on currently available workers. It does not provision fresh capacity by itself. If the funded floor is missing, the endpoint returns a shortfall quote. Fund the account and retry.","purpose":"Prepare the published project runtime on currently available matching workers (owner only)"},{"path":"/api/v1/projects/:name/invalidate","response":"200: {status, ready_for_jobs, status_message, next_action, init_status}","method":"POST","purpose":"Mark prepared runtime state as stale for the latest revision so future jobs or optional manual init rebuild it cleanly (owner only)"},{"path":"/api/v1/projects/:name/cancel-queued","response":"200: {status, project, tag?, queued_before, cancelled_jobs}","body":{"reason":"optional string","tag":"optional string"},"method":"PUT","purpose":"Cancel queued or planning jobs for one project without listing them individually (owner only)"},{"path":"/api/v1/projects/:name/kv","response":"200: {project, prefix, entries: [{key, value, updated_at, ttl_s?, meta}], count}","method":"GET","query_params":"?prefix=checkpoint&limit=100","purpose":"List project-scoped shared state entries for orchestration and cross-job coordination (owner only)"},{"path":"/api/v1/projects/:name/kv/:key","response":"200: {project, key, value, updated_at, ttl_s?, meta}","body":{"value":"any JSON value","ttl_s":"optional positive integer"},"method":"PUT","purpose":"Write a small JSON shared state value for one project (owner only)"},{"path":"/api/v1/projects/:name/kv/:key","response":"200: {project, key, value, updated_at, ttl_s?, meta}","method":"GET","purpose":"Read one project-scoped shared state value (owner only)"},{"path":"/api/v1/projects/:name/kv/:key","response":"200: {status: \"deleted\", project, key}","method":"DELETE","purpose":"Delete one project-scoped shared state value (owner only)"},{"path":"/api/v1/projects/:name/status","response":"200: {project, project_ready, can_accept_new_jobs, ready_for_jobs, init_state, availability_status, progress_phase, pending_init, requested_state?, content_hash, status_message, next_action, latest_issue?, last_ready_at, latest_activity_at?, latest_failure_at?, ready_replicas, initializing_replicas, failed_replicas, stale_replicas, total_replicas, queue_depth, queued_tasks, active_jobs, active_tasks}","method":"GET","note":"This is the public readiness view for the active revision. Use progress_phase plus the replica counts to distinguish queued init, active init, and failed-with-no-active-attempt states. Machine identities stay internal.","purpose":"Public readiness status for the project"},{"path":"/api/v1/projects/:name/status/details","response":"200: {project, project_ready, can_accept_new_jobs, ready_for_jobs, init_state, availability_status, progress_phase, pending_init, requested_state?, content_hash, status_message, next_action, latest_activity_at?, latest_failure_at?, ready_replicas, initializing_replicas, failed_replicas, stale_replicas, total_replicas, queue_depth, queued_tasks, active_jobs, active_tasks, diagnostics: [{id, status, phase, message, log_tail, content_hash, inserted_at, updated_at, initialized_at, recommended_action}], last_ready_at}","method":"GET","purpose":"Public readiness plus sanitized diagnostics for debugging setup or refresh issues"},{"path":"/api/v1/projects/:name/stream","response":"200 text/event-stream. Events: snapshot (initial active/queued/recent jobs), job (delta on any change), timeout (after 1h — reconnect).","method":"GET","note":"This endpoint fits clients that submit many jobs to the same project. Open one stream, submit jobs, and watch them complete. Per-job polling is not necessary. After the server timeout, reconnect.","purpose":"SSE stream of all job activity in a project. One persistent connection replaces per-job polling. Scoped to the API key of the caller (admins see all jobs)."}],"presets":[{"path":"/api/v1/presets","method":"GET","purpose":"List available resource presets, including common training shapes and requirements"}],"artifact_workflow":{"description":"To produce artifacts, a task writes files under $COMPUTALOT_ARTIFACT_DIR and declares relative paths in payload._artifacts.upload. Worker and client uploads use the authenticated controller relay. Register existing remote objects through POST /api/v1/artifacts/external. Direct and multipart object-store upload endpoints return 410 Gone. Artifact IDs appear in task results under 'artifact_ids'.","access_control":"Artifact IDs are account-scoped. Job submission validates every concrete _artifacts.download ID and records retained input references atomically, before Computalot creates work or places a billing hold. The default retained-byte quota is 100 GiB per account. When the quota is full, the API returns 507 artifact_quota_exceeded.","env_vars":{"COMPUTALOT_ARTIFACT_DIR":"The directory for task output files. Declare relative regular-file paths under this directory in payload._artifacts.upload. The API rejects absolute paths, symlinks, and paths outside this root.","COMPUTALOT_TASK_RESULT":"The path for the JSON result file (artifact_ids are auto-appended)."},"retention":"GET /api/v1/artifacts returns the authoritative quota limit, used, and remaining bytes, plus size, created_at, last_referenced_at, reference_count, active_reference_count, terminal_reference_count, and deletion_eligible per artifact. The quota counts each active local or R2 content-addressed backing object once per account, not duplicate metadata rows. DELETE refuses active-job references, but terminal-job references are informational and do not block owner deletion. An accepted deletion hides metadata and releases account quota immediately. Computalot removes namespaced backing objects after the default 24-hour grace period. Legacy backing objects stay in guarded operator inventory and do not consume user quota."},"batch":[{"path":"/api/v1/jobs/batch","response":"201/207: {jobs: [{index, id, status, payload, meta, variant?, ...}], submitted: N, errors: [{index, error, recommended_action?}], error_count: N}","body":{"jobs":"[array of job submission objects]"},"method":"POST","purpose":"Submit multiple jobs at once (max 200)"}]}}