Estimate first — budgetUsd is optional there — then ask the user for budgetUsd (never choose one for them) and call submitJob only after the user has approved the estimate at that budget. Run a containerized workload on a rented cloud machine sized to the spec — GPU, and as much disk as minDiskGb asks for, so the local machine's disk and bandwidth are irrelevant (the machine downloads datasets itself). Queues the job and returns a durable executionId immediately (status "queued"); Symbioza provisions, runs, collects the output files and tears down, all under the budgetUsd ceiling. Write every deliverable to /workspace/artifacts/ — only that directory is collected and returned by getArtifact; a command that saves its model to the working directory or any other path finishes with nothing delivered. image must be one of the images we have verified launch on our machines: pytorch/pytorch:2.4.1-cuda12.1-cudnn9-runtime, pytorch/pytorch:2.4.0-cuda12.1-cudnn9-runtime, tensorflow/tensorflow:2.15.0. Any other image is refused with code invalid_spec, action fix_spec and a supportedImages list, before anything is booked or charged. budgetUsd is what you are willing to spend on the job, and you are never billed more than that. Call estimateExecution first if you have not priced this spec — it also tells you whether this account can cover the budget, and whether it holds free compute credit still to claim. A refusal here carries a machine-readable code (unauthorized, invalid_spec, budget_too_low, insufficient_balance, access_required, secret_conflict, no_compute, unavailable, refused) and one action (authenticate, fix_spec, raise_budget, add_funds, claim_credit, add_access, relax_spec_or_retry, retry_later, contact_support), so branch on those rather than on the sentence; a refusal books no machine and charges nothing. insufficient_balance with action claim_credit means free credit is waiting on the account — claim it at https://symbioza.dev/app and resubmit; insufficient_balance also carries shortfallUsd and topupUrl. access_required and secret_conflict carry the same access block estimateExecution returns, with a Symbioza setup link for each missing saved secret: give the user that link, and after they say it is saved call listSecrets and submit again. On acceptance each attached set is pinned to its id. OPTIONAL: if your workload can count its own work, write /workspace/artifacts/.symbiosa/metrics.json with {"unit": "configuration", "units_planned": 100, "units_completed": 100, "elapsed_seconds": 4821} — unit is free text naming what you counted, partial progress is valid evidence on a failed run, and the whole file is optional (omit it and throughput is simply recorded as UNKNOWN; do not invent a unit to fill it in). It is recorded as observed evidence about your workload and never affects what you are charged; a missing, malformed or incoherent file changes nothing about the run. You may disconnect and poll getStatus later. If an attempt fails and the job is moved to a replacement machine, the newest checkpoint streamed off the previous box (files named ckpt_step.pt, N = digits) is pushed onto the new box under /workspace/.symbiosa/resume/ (outside the artifacts directory) and SYMBIOSA_RESUME is set to its absolute path there — your command must load that file to continue, or the attempt restarts from the beginning. The variable is unset on a first attempt, when no checkpoint was kept, when kept checkpoints span more than one directory (multi-arm layouts are not restored), when checkpoint.enabled is false, when the box lacks disk headroom, or when the push failed; restore is best-effort salvage and never fails a run. Work since the last streamed checkpoint is lost, and the push's hash check proves the transfer, not that the checkpoint itself is intact. Only when the user explicitly requires a particular model for their own benchmark, set gpu.gpuNameExact: true alongside gpu.gpuName to pin the CARD MODEL: only that model is booked and every substitute is refused, including a faster one, because when the measured quantity is the hardware a better card is as wrong as a slower one; it holds the model across every run. pinHost is accepted and ignored. The same model is sold at different board powers under one name, so for an energy or efficiency measurement also declare gpu.minPowerLimitW (watts): hosts below it are refused, and a host publishing no power figure is refused rather than assumed un-throttled. Declare gpu.minCudaVersion (e.g. 13.0) when your image pins a torch/flash-attn build against a CUDA version: only hosts whose driver advertises at least that CUDA are booked, and a host that publishes no CUDA ceiling is refused rather than booked. Declare datasetGb as the total data the box will ingest, including data it downloads itself (not only what datasets[] stages), so a host too slow to pull it in time is filtered before booking. With a clientRequestId, the same key with the same spec returns the same job, marked duplicate: true, instead of booking and charging for a second one; the same key with a different spec is refused with invalid_spec. Without a key, an identical submission (same spec and budget) from the same account returns the same job marked duplicate: true and deduplicated: true and books nothing while that job is still queued or running, at any time after it failed, was cancelled or was lost (then the response carries a hint instead of starting a second paid attempt), and within 10 minutes of its acceptance if it delivered. Keep one clientRequestId for one submission across every resend. To run a failed, cancelled or lost job again, call submitJob with retryOf set to its executionId and a new clientRequestId: that starts a fresh attempt at once, checked against the budget and balance again, even with the same spec, and its response names the earlier job as retryOf; a retryOf that is not a failed, cancelled or lost job of this account is refused with invalid_spec. A new clientRequestId always starts a new job. Pass the specDigest estimateExecution returned and a spec or budget changed since that estimate is refused with invalid_spec, so what runs is what you were quoted. A job whose command, image or environment names cryptocurrency-mining software or a mining pool is refused under the Terms of Service (code refused, action contact_support); nothing is booked and nothing is charged.