{"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"},"recent_changes":{"items":["Bring-your-own-hardware early access: a machine 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), declares the resources it lends at install time, and runs every task inside a rootless gVisor sandbox. See /docs/hosting.","Controller-side project OCI builds are durable and asynchronous: POST /push returns 202 with push_ref + status_url, the prior revision remains active, and status exposes published/failed state with bounded build diagnostics.","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 on the funding rail: quote 402 responses carry a WWW-Authenticate Payment challenge (EVM charge), quotes settle with an Authorization: Payment credential carrying an EIP-3009 authorization, and 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; 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. Local/R2 content hashes are deduplicated within an account; quota exhaustion returns 507 artifact_quota_exceeded.","Artifact owners can delete as soon as every referencing job is terminal. Active jobs still return 409 artifact_in_use; deletion releases account quota immediately, GET /api/v1/artifacts reports used/remaining quota, and namespaced backing data has a default 24-hour GC grace.","Worker process exit status is terminal truth. result_quality/result_warnings are reserved as null/empty. Legacy object-shaped result_schema metadata is accepted and ignored.","Job admission validates retries, dependencies, fan-out, resources, and account-owned artifact inputs before creating work or placing billing holds.","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."],"details":"/docs/changelog","as_of":"2026-08-05"},"project_lifecycle":{"setup":"Include a Dockerfile and computalot.project.json manifest. Controller-side OCI builds return 202 after durable staging; poll status_url until published or failed. The prior revision remains 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 tarball (with Dockerfile + computalot.project.json + your code) -> Submit jobs. Optional: call init to prepare currently available workers ahead of time.","debugging_init":"Use GET /api/v1/projects/:name/status for readiness, then GET /api/v1/projects/:name/status/details for diagnostics and recovery steps.","update_flow":"Push new tarball -> optional POST /invalidate if you want to discard old prepared runtimes -> submit jobs normally; the first job on the new revision may cold-start while runtime preparation catches up"},"hosting":{"join":"On the machine, as root: `curl -fsSL https://computalot.com/computalot-host -o computalot-host && chmod +x computalot-host`, then `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 machine's configuration; nothing is copied to the machine by hand and nothing ever connects in — the worker only dials out.","summary":"Bring your own hardware: a machine you control joins the fleet as a permanent worker with a one-time enrollment code, declares the share of itself it lends, and runs every task inside a rootless gVisor sandbox. Early access — enrollment codes are issued by the Computalot team.","isolation":"Four boundaries protect the machine from the work it runs: each task executes in its own rootless gVisor sandbox (a userspace kernel — tasks never talk to the host kernel directly); each task gets hard cpu/memory/process caps inside the lend; task traffic leaves through a private network namespace whose egress policy rejects the machine's own addresses, loopback services, and the local network — only the internet is reachable; and 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 the installer created. Identity, the machine's rate, and the kinds of work it may 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/--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) and is exactly what the machine reports upward as its capacity.","status_note":"Early access: today's enrolled hardware is operator-run, and host earnings/payouts are still being built (each machine's hourly rate is recorded at enrollment). To join the program, use POST /api/v1/feedback (type feature_request) or the waitlist at /."},"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. Placement decisions, warm homes, and cluster topology are managed internally by Computalot.","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"]},"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, readiness is still checked 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; useful for distinguishing a requested refresh from steady-state readiness","can_accept_new_jobs":"true when the latest project revision is published and can be submitted; the first job may still pay a cold-start cost while runtime capacity is prepared","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 waiting for runtime preparation; this is not by itself 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","latest_activity_at":"most recent sanitized init activity timestamp across diagnostics, when available","latest_failure_at":"most recent sanitized failed-attempt timestamp, when available","latest_issue":"optional sanitized setup error summary when attention is required","pending_init":"true when Computalot currently has project init work queued or in flight","progress_phase":"\"queued\", \"running\", \"failed\", \"refreshing\", \"refresh_pending\", \"ready\", or \"idle\" so clients can tell waiting-for-capacity from active setup and terminal failure"},"summary":"Project readiness is exposed as active revision truth managed by Computalot, 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 issues: 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 and status/details when you need recovery guidance."},"description":"Computalot is a distributed compute platform. Submit jobs, get structured JSON results. GPU and CPU workers.","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 try every combination of parameters and rank results. Grid search / hyperparameter tuning.","use":"sweep"},{"when":"You want to split a numeric range into chunks, process in parallel, and aggregate with operators (sum, mean, max, etc).","use":"map_reduce"},{"when":"You want to compare 2+ named candidates with replicas for statistical significance.","use":"benchmark"}],"jobs_vs_tasks":{"summary":"A job is a unit of work you submit. Tasks are the parallel units Computalot creates from your job. When you need to run the same script on 1000 different inputs, you can either submit 1 job that fans out into 1000 tasks, or submit 1000 separate single-task jobs. This section helps you choose.","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.","Each job can be watched 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 — see '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 should not affect each other on failure (e.g. processing 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 to let Computalot expand 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.","Results are accessed per-task via 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":["Logically related work that should succeed or fail together (e.g. a grid search, a chunked simulation, a benchmark comparison)","Work where you want a single aggregated result (sweep leaderboard, map_reduce aggregation, benchmark statistics)","Batch processing where individual items don't 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 should succeed or fail on their own, use 1000 jobs. When in doubt, start with one job — it's simpler to manage and faster to submit."},"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"},"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. Allowed declarative validation fields include executables and files; runtime.init.commands, runtime.services, and validation.commands are rejected."},"feedback":{"types":["bug","feature_request","provisioning","job_type_request"],"endpoint":"POST /api/v1/feedback","note":"No auth required. Report bugs, request features, share ideas."},"name":"computalot","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 needed. API keys remain issued on request via the waitlist at /. Install the skill from /skill.md to get started, and use POST /api/v1/feedback (no auth) to report bugs and request features.","runner_protocol":{"progress":"Print COMPUTALOT_PROGRESS:{json} to stdout for live progress updates.","summary":"All job types use the runner protocol. Your script receives input and writes output via environment variables.","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":"Directory for output files (models, checkpoints). Auto-uploaded on completion.","COMPUTALOT_TASK_CACHE_DIR":"Project-scoped cache shared by ALL tasks running on the same node (survives across tasks). Concurrency contract: concurrent tasks see the same directory, so never stage a build in it under a deterministic temp name — two tasks building the same content race on the final rename (FileNotFoundError in pathlib .replace / shutil copy). Safe publish pattern: write to a per-task unique temp in the SAME directory, then atomically rename 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 dir (advisory locks are supported on the cache mount). Check for the final file before building to skip redundant work.","COMPUTALOT_TASK_PAYLOAD":"Path to JSON input file. Read this.","COMPUTALOT_TASK_RESULT":"Path to write your JSON output. Computalot reads this after exit.","COMPUTALOT_TASK_SCRATCH_DIR":"Writable temp directory for scratch files."},"exit_code":"Exit 0 = success. Non-zero = failure."},"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. Job is accepted and 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 and are rejected rather than silently stored.","example":{"reservation":{"mode":"best_effort"}},"current_behavior":{"submit_failure":"POST /api/v1/jobs returns 422 for guaranteed or capacity-hold fields so no unenforced guarantee can be accepted.","submit_success":"Omit reservation or pass {mode: best_effort} for normal queueing."}},"documentation_updated_at":"2026-08-05","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 so agents and clients anchor themselves on the live public endpoint.","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)"]}},"billing":{"summary":"Computalot uses one account-level credit system for API-key callers and wallet-authenticated agents.","pricing":{"details":"/docs/pricing","authoritative_quote":"The submit response's summary.billing_estimate is the authoritative per-job quote (its 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":"Metered compute billed per task-second at Computalot's rate for the requested resource class — the live market rate for that capacity plus a 35% service markup — with a 60-second minimum billed duration per task-attempt. Prepaid credits; a refundable hold is reserved at submit and settles to the metered charge at terminal state. Queue time is never charged, and timeout_s caps each task's runtime cost.","no_extra_charges":"No subscriptions, no idle charges, no separate artifact storage/transfer fees today (100 GiB retained-artifact quota per account applies). Project init is free above the $5.00 available-balance floor."},"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 so you can see which job admissions are still reserving funds.","quotes":"GET /api/v1/account/quotes lists open top-up and shortfall quotes so clients can inspect pending funding actions before retrying blocked work.","ledger":"GET /api/v1/account/ledger is the settled transaction history for credits, debits, and other posted account activity."},"mpp":"MPP (Machine Payments Protocol, mpp.dev) is accepted 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 402 body's `mpp` block includes the decoded request). Settle by POSTing the quote's pay URL — or /api/v1/account/quotes/topup — with `Authorization: Payment <base64url credential>` carrying an EIP-3009 authorization (the same proof as the x402 exact scheme; no bearer token needed). 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, 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 and retry `POST /api/v1/projects/:name/init`.","shared_retry_rule":"A shortfall response blocks admission before work starts. Do not mutate the project or payload first — once the funding gap is fixed, retry the same request."},"supported_access_paths":{"api_key":"Use an API key when access was provisioned for you directly. It authenticates the same account surfaces, including 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, can pay x402 quotes, and reaches the same account billing truth."},"v1_policy":"Job execution reserves a bounded submit-time hold for the initial attempt plus requested max_retries and settles to actual internal cost-ledger totals at terminal completion. Infrastructure requeues do not consume the configured retry budget. Project init and artifact download remain free in v1, but are still metered 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 using PAYMENT-SIGNATURE. Submit-time or init-time insufficient-balance responses may also include a shortfall quote with PAYMENT-REQUIRED."},"result_quality":{"summary":"result_quality and result_warnings are reserved response fields and are currently returned as null and an empty list. They do not affect terminal status.","custom_validation":"Custom result_schema validation is not implemented. Legacy object-shaped result_schema metadata is accepted for client compatibility and ignored; validate result contents in your runner and use its exit status as terminal truth."},"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"},"summary":"Submit minimum resource needs with a job instead of targeting infrastructure directly. Computalot may 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}}},"heavy_job_guidance":{"summary":"For GB-scale datasets, large checkpoints, and long training runs, treat the job payload as control-plane data only.","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 so the worker prepares them before launch instead of downloading ad hoc 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. Resolve upstream artifact IDs from GET /api/v1/results/:job_id before submitting the downstream job.","payload._shared.resolve is not supported. Read project shared state through GET /api/v1/projects/:name/kv before submission and put the resolved value directly in the job payload.","Project-scoped shared state lives behind GET/PUT/DELETE /api/v1/projects/:name/kv/:key. It is not injected automatically at dispatch.","Resolved artifact paths are injected into payload._artifacts.local_paths. Single-file entries also get COMPUTALOT_ARTIFACT_<NAME> env vars."],"operational_defaults":["Prefer external/object storage artifacts for multi-GB datasets and model bundles.","Declare cache_mounts for writable package/model caches your code populates at runtime. For Hugging Face or Transformers downloads, use a huggingface cache mount so HF_HOME and TRANSFORMERS_CACHE persist per worker.","hf-mount only applies 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 so retries can resume instead of restarting from zero.","Set timeout_s with headroom above expected task runtime after worker start; queue time is managed by internal controller policy.","Start project setup before the run so initialization does not consume the first training attempt."],"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 and are relayed 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."]},"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."},"version":"v3","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 are queryable for 30 days. Artifact owners may delete once every referencing job is terminal; deletion releases account quota immediately and schedules namespaced backing-object removal after the default 24-hour grace. 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; each attempt's history 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 (instance vanished mid-run) requeue the task automatically with the budget refunded, uncapped, with attempt.infra_requeued / attempt.lost events for visibility. If a task repeatedly dies with exit 137, raise its memory expectations rather than assuming a code bug.","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"}},"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; no allowlist entry is required.","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 operator-gated; /metrics requires admin auth, a dedicated metrics token, or a local request. Other endpoints require a bearer token: either a wallet session (any wallet, via 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 needed. flk_ API keys are issued on request: join the waitlist at / or ask the team.","roles":{"member":"Default role — create/manage projects, jobs, and artifacts under your own account.","admin":"Operator role — full platform access via the admin API surface."},"scoping":"Endpoints are scoped by account ownership. API keys and wallet sessions both resolve to an account. You see projects you own and jobs submitted under that account. Infrastructure identities are not exposed in public responses because placement is managed internally by Computalot."},"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, and 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 remains 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, then loads project env files and applies meta.env overrides."},"controller_url":"https://computalot.com","status":"public_beta","debugging_failures":{"summary":"When a job or task fails, check these endpoints.","common_issues":{"402 Payment Required":"Fund the account and retry the same request.","Setup fails":"Fix Dockerfile/manifest or runtime issue, POST /invalidate, then submit a job normally or call POST /init to prepare currently available workers.","Tasks stuck in queued":"Check project status — the revision may be published but still waiting on runtime capacity or initialization.","exit_code_1 with no useful error":"Check per-task output field for full 10KB output."},"steps":["1. GET /api/v1/jobs/:id — check 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"]},"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":"bash, sh, zsh, and other shell executables are blocked.","example_invalid":["bash","-c","python train.py"],"example_valid":["python","train.py"]},"getting_started":{"overview":"Computalot runs jobs on managed CPU/GPU capacity. Authenticate with an API key or any wallet (challenge/verify — no approval needed). Billing truth lives on the account balance/holds/ledger/quotes endpoints. Fund the account when needed, set up a project environment, then submit best-effort jobs with the resource minimums 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":"Single task. For parallelism, add fan_out: {by: \"models\"} to split a list field into N tasks, or 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":"Creates 9 tasks (3x3 grid). Each task receives one parameter combination in $COMPUTALOT_TASK_PAYLOAD. Results ranked 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":"Creates 50 tasks. Each gets {seed_start, seed_count} in payload. Results aggregated with per-field operators. You can also use split.ranges for explicit non-contiguous ranges, e.g. %{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":"Creates 9 tasks (3 candidates x 3 replicas). Each gets candidate config + _candidate + _replica in payload. 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. POST /api/v1/account/quotes/topup and settle it with POST /api/v1/account/quotes/:quote_id/pay/x402 if your account needs credits — 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 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 tarball (raw binary body); poll the returned status_url when the response is 202","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 if you want to prepare currently available workers ahead of time. 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 merely 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 (recommended), 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 many endpoints","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"]},"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 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 required. Any well-formed wallet is accepted."},{"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 required. 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":"Recommended way to read task outcomes. The 'result' field contains the JSON 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/result_warnings are reserved and currently null/empty. 'summary' includes aggregate_result, aggregate_aliases, completeness, task_outcome_counts, and 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 like chunk_index, chunk_count, and seed_range stay visible per task. 'artifact_ids' lists files produced by the task 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 remains the canonical lookup key. For live retry-loop diagnostics, prefer GET /api/v1/jobs/:id/output and GET /api/v1/jobs/:id/tasks because they preserve the latest failed-attempt output/error even before the next attempt finishes. Placement infrastructure is not part of the public result surface, so 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 are malformed (non-integer, limit <= 0, offset < 0).","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&user_id=42&group_by=project&include_tasks=false","note":"Defaults to terminal statuses (completed, partial, failed, cancelled) rather than only completed jobs. Pagination uses limit (1-100, default 20) and offset (>= 0, default 0); malformed values return 422 with a specific error message, and the response echoes both back. Returns applied_filters, a result-guide block, and per-job links so 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. Returns 402 Payment Required advertising both rails — x402 in the PAYMENT-REQUIRED header/body and MPP in the WWW-Authenticate: Payment header plus the body's `mpp` block — or 422 when amount_usd is malformed, not positive, or exceeds the $10,000 per-top-up cap. Re-POSTing this path with an MPP `Authorization: Payment` credential settles the quote the credential references instead of creating a new one."},{"path":"/api/v1/account/quotes/:quote_id/pay/x402","method":"POST","purpose":"Settle a quote and credit the account on success. Two credential carriers: x402 via the PAYMENT-SIGNATURE header (bearer auth required), or MPP via `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). First settlement returns 201, replay-safe repeats return 200 with replay=true, malformed credentials return 422, and 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":"Body is streamed directly to disk — 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 (includes artifacts from your jobs, even if they were uploaded automatically during 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, and artifact metadata may 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 job input belongs to a non-terminal job. Once all references are terminal, deletion hides metadata and releases account quota immediately, then schedules namespaced backing-object GC after the default 24-hour grace."}],"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. 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. Per-task command and routing overrides would bypass the validated runner contract.","parameters":"map of param_name -> [values] (sweep only). Cartesian product, max 1000 combos.","max_retries":"int 0-10 (default 0) — upper bound for workload retries after the initial attempt. Cause-aware policy retries only when the next attempt can improve the outcome: infrastructure loss and runner crashes requeue without consuming 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 at most 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/result checkpoint maps under `checkpoint`; Computalot durably publishes artifact-backed checkpoints when an `artifact_id` is present or a checkpoint path is publishable, and retries recover that state into the next task payload.","requirements":"object (optional) — minimum cpu, memory_mb, storage_gb, gpu_count, gpu_memory_mb, profile for each task","timeout_s":"int (default 3600) — per-task runtime cap after a worker starts the task; queue time is internal controller policy","callback_url":"reserved — non-empty values return 422 until durable webhook delivery is available; use job SSE/watch or polling","reservation":"object (optional) — only {mode: best_effort} is supported. guaranteed, parallelism, guaranteed_for_s, and max_wait_s return 422.","preset":"optional string — a resource preset name from GET /api/v1/presets (e.g. gpu_batch). Seeds default requirements and the billing class; explicit requirements fields take precedence over preset values. Unknown names return 422.","runner_command":"array — e.g. [\"python\", \"script.py\"]. Required.","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)","merge_strategy":"collect | keyed | weighted_avg (structured_runner only)","reliability_mode":"optional string — best_effort | strict_complete. Recommended for research-sensitive fan-out work.","result_schema":"reserved — omit this field. Non-empty values return 422 until authoritative result-schema validation is implemented.","runtime_hint_s":"optional int — expected runtime hint used by hold estimation. timeout_s remains the runtime hard ceiling after worker start."},"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.","method":"GET","query_params":"?status=queued&project=my-proj&type=structured_runner&tag=sweep_72&limit=50&offset=0","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; 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 preserves the most recent failed attempt's diagnostics 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, checkpoint resumes, unclassified legacy retries, and policy-suppressed retries and includes the last failure class/decision when available. During auto-retry, queued/running tasks can continue showing the previous failed attempt's output/error until the current attempt emits its own diagnostics. Checkpoint state can include durable publication fields like artifact_id, artifact_source, publish_status, and published_at; resume payloads can include artifact-backed checkpoint download metadata. For failed tasks, result may be a machine-readable failure payload with fields such as failure_kind, exit_code, command, cwd, and combined_output. Public task responses keep the submitted task payload contract, but they do not expose current_node, provider IDs, raw runtime paths, or image refs/digests.","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":"More efficient than opening one /stream per job. Best for batch submissions where you need to track 2-100 jobs. Idle periods emit ping keepalives. Snapshot and terminal job frames include client_ref, tags, meta, variant, plus the same public summary fields exposed by GET /api/v1/results/:job_id, including aggregate_result, aggregate_aliases such as avg_edge when available, completeness coverage, and 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, 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."}],"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/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 if 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 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 body is not valid gzip, 409 if another push or 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 and does not provision fresh capacity by itself. If the funded floor is missing, 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. Clients should use progress_phase plus the replica counts to distinguish queued init, active init, and failed-with-no-active-attempt states. Machine identities remain 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":"Recommended for clients that submit many jobs to the same project. Open one stream, submit jobs, and watch them complete — no per-job polling needed. Reconnects automatically after server timeout.","purpose":"SSE stream of all job activity in a project. One persistent connection replaces per-job polling. Scoped to the caller's API key (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":"Tasks can produce artifacts by writing files under $COMPUTALOT_ARTIFACT_DIR and declaring relative paths in payload._artifacts.upload. Worker and client uploads use the authenticated controller relay; existing remote objects can be registered 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 verifies every concrete _artifacts.download ID and records retained input references atomically before creating work or placing a billing hold. The default retained-byte quota is 100 GiB per account; quota exhaustion returns 507 artifact_quota_exceeded.","env_vars":{"COMPUTALOT_ARTIFACT_DIR":"Directory for task output files. Declare relative regular-file paths under this directory in payload._artifacts.upload; absolute paths, symlinks, and paths outside this root are rejected.","COMPUTALOT_TASK_RESULT":"Path to write JSON result file (artifact_ids are auto-appended)."},"retention":"GET /api/v1/artifacts returns authoritative quota limit/used/remaining bytes plus size, created_at, last_referenced_at, reference_count, active_reference_count, terminal_reference_count, and deletion_eligible per artifact. Quota counts each active local/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. Accepted deletion hides metadata and releases account quota immediately; namespaced backing objects are removed after the default 24-hour grace, while legacy backing objects remain in guarded operator inventory without consuming 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)"}]}}