CLI
unitfabric is the command line for the HTTP API: pip install unitfabric (Python 3.10 or later). uf is the same command under a shorter name, and grid, the name it had before, still runs it in this release.
$ unitfabric --helpusage: unitfabric [-h] command ...
unitfabric (or uf): submit batch jobs priced per result. unitfabric login --url URL [--key KEY] (or set GRID_API_KEY; prompts otherwise; GRID_CONTROL_URL + GRID_API_KEY also work without a login) unitfabric templates | unitfabric account | unitfabric ledger [--all] unitfabric topup AMOUNT (prints a card-checkout link) unitfabric upload FILE [--name NAME] (a unit's input file, up to 4 GiB; prints the key a manifest line's url names it by) unitfabric submit job.yaml (input: a local .jsonl path is uploaded first; or an r2:// / file:// key) unitfabric jobs [--all] | unitfabric job ID | unitfabric units ID [--status failed] [--json] | unitfabric cancel ID unitfabric accept ID [--price P] (a paused job runs on at its new price; with --price, only if that is still P) unitfabric results ID --out DIR (downloads every artifact of every done unit; safe to rerun) unitfabric keys list | unitfabric keys create NAME | unitfabric keys revoke ID unitfabric machines (the org's machines: status, rate, trust and start trust, others' jobs, today) unitfabric machines confirm ID (a machine enrolled with the org's token takes the org's own jobs once confirmed) unitfabric machines set ID [--name N] [--rate R] [--pause | --resume] [--private | --public] [--accept-byo | --no-byo] unitfabric machines remove ID unitfabric supply tokens [list | create NAME | revoke ID] (a supply token installs machines for a day; shown once) unitfabric supply earnings [--all] [--daily DAYS] | unitfabric supply rates | unitfabric supply payouts unitfabric supply onboard [--country US] (prints the Stripe page that sets up payouts)
positional arguments: command machines the org's machines supply supply tokens, earnings, rates and payouts
options: -h, --help show this help message and exitCredentials
Section titled “Credentials”unitfabric login --url <control url> --key <key> (the dashboard’s API keys page shows this line with the URL filled in) checks the key against the control plane, then saves the URL and key in ~/.config/grid/config.json (in $GRID_CONFIG_DIR if that is set), readable by you only. Without --key it reads GRID_API_KEY, or asks. When GRID_CONTROL_URL and GRID_API_KEY are both set, every command uses them instead of the saved login, which suits scripts and CI.
A command that fails prints one line starting unitfabric: and exits with status 1; a usage error exits 2, an interrupted command 130.
Commands
Section titled “Commands”unitfabric login --url <control url> --key grid_...Prints logged in to <url>; balance $<balance>; credentials in <path>. A key that the control plane refuses is not saved, and an http:// URL for another machine draws a warning: the key would travel in clear.
templates
Section titled “templates”$ unitfabric templatesid family list_price_usd unit_max_secondsaudio/whisper-large-v3@1 whisper 0.004000 600image/flux-schnell@1 flux 0.004000 180image/sdxl-turbo@1 sdxl 0.001500 120render/blender@1 blender 0.020000 3600text/embeddings@1 embeddings 0.001000 900video/transcode@1 transcode 0.004000 600Every template with its price per unit and its time limit per unit, in seconds. The pages under Templates describe each unit.
account
Section titled “account”unitfabric accountPrints the organization’s id, name and balance as JSON.
ledger
Section titled “ledger”unitfabric ledgerunitfabric ledger --allThe newest 200 credits and charges, with created_at, kind, amount_usd, job_id and reference; --all pages through every entry.
unitfabric topup 25Prints a card checkout link for $25; the credit appears within a minute of paying. The amount is 5 to 10,000 US dollars with at most two decimals. Type it without a dollar sign: bash turns $25 into 5, and zsh drops it entirely.
upload
Section titled “upload”$ unitfabric upload take-01.mp4take-01.mp4: 23.4 MiB of 23.4 MiB uploaded (100%)r2://grid/uploads/org_ists4trnmppr/up_kw82s6htarhx/take-01.mp4Uploads a unit’s input file, up to 4 GiB, and prints its key alone on stdout: the exact string a manifest line’s url names the file by (see uploading inputs), so $(unitfabric upload <file>) can write it into a manifest. A file over 8 MiB shows its progress on stderr: on a terminal one line, rewritten as the upload goes; anywhere else that line once, when the upload is done. --name sets the name the key ends in; by default it is the file’s own, with any character other than a letter, a digit, ., _ or - made _. A file over the limit, or a name the control plane does not take, is refused before anything is sent:
$ unitfabric upload huge.mkvunitfabric: error 422: huge.mkv (5368709120 bytes) is too large to upload: bytes: Input should be less than or equal to 4294967296$ unitfabric upload take-01.mp4 --name "take 1.mp4"unitfabric: error 422: 'take 1.mp4' cannot name an upload: use letters, digits, '.', '_' and '-', starting with a letter or digit, at most 255 characterssubmit
Section titled “submit”$ unitfabric submit job.yamluploaded clips.jsonl as r2://grid/uploads/org_ists4trnmppr/up_25iq9pbxjfki/clips.jsonljob_9nww8dhegrj5 queued units=2 price/unit=0.004000Prints the job’s id, status, number of units and price per unit (— while a bring-your-own job is calibrating). A local input path, relative to the spec file, is uploaded first and the CLI prints uploaded <path> as <key>; input can also be the storage key of an earlier upload. A manifest is at most 32 MiB, and a larger one is refused before it is sent: large inputs go up as files of their own with unitfabric upload, and the manifest’s lines name their keys. The spec is checked before anything is uploaded: unknown fields are refused, and price must be a mapping. YAML is read by 1.2 rules, so checkpoint: off stays the word off and a date in name stays text. Each submit carries its own client token, so a request that times out is retried without creating a second job. pool says whose machines may run the job (see pools).
$ unitfabric jobsid name status units_total done failed cost_usd price/unitjob_9nww8dhegrj5 first clips queued 2 0 0 0.000000 0.004000job_5vecatwij3e8 archive checksums paused 2000 32 0 0.160000 0.005000 now, 0.015000 proposed (units take over twice their calibrated time; your uncommitted credit does not cover the new price)job_b4xy9buq23dn checksums paused 120 32 0 0.160000 0.005000 now, 0.015000 proposed (units take over twice their calibrated time; the new price is above the job's max_per_unit)The newest 200 jobs; --all pages through every job. A paused job’s price per unit is the one it ran at, the one it waits for you to accept, why it was quoted again, and why the new price waits for you: it is above the job’s max_per_unit, or your uncommitted credit does not cover it (see accept).
$ unitfabric job job_9nww8dhegrj5job_9nww8dhegrj5 queued pending=2 leased=0 done=0 failed=0 cost=$0.000000 price/unit=0.004000$ unitfabric units job_9nww8dhegrj5unit_id status attempts seconds price_usd failure_reasontake-01 pending 0take-02 pending 0--status pending|leased|done|failed lists the units in one state; --json prints every field, including the end of a failed unit’s log (log_tail).
cancel
Section titled “cancel”$ unitfabric cancel job_9nww8dhegrj5cancelledUnits that are not done fail with the reason cancelled, and nothing more of the job is billed.
accept
Section titled “accept”$ unitfabric job job_b4xy9buq23dnjob_b4xy9buq23dn paused pending=88 leased=0 done=32 failed=0 cost=$0.160000 price/unit=0.005000 now, 0.015000 proposed (units take over twice their calibrated time; the new price is above the job's max_per_unit)accept the new price, or cancel the job: unitfabric accept job_b4xy9buq23dn --price 0.015000 unitfabric cancel job_b4xy9buq23dn$ unitfabric accept job_b4xy9buq23dn --price 0.015000unitfabric: the new price waiting is 0.045000, not 0.015000, so nothing was accepted: run `unitfabric job job_b4xy9buq23dn` to see it$ unitfabric job job_b4xy9buq23dnjob_b4xy9buq23dn paused pending=72 leased=0 done=48 failed=0 cost=$0.400000 price/unit=0.015000 now, 0.045000 proposed (units take over twice their calibrated time; the new price is above the job's max_per_unit)accept the new price, or cancel the job: unitfabric accept job_b4xy9buq23dn --price 0.045000 unitfabric cancel job_b4xy9buq23dn$ unitfabric accept job_b4xy9buq23dn --price 0.045000job_b4xy9buq23dn running pending=72 leased=0 done=48 failed=0 cost=$0.400000 price/unit=0.045000$ unitfabric accept job_b4xy9buq23dn --price 0.045000job_b4xy9buq23dn running pending=72 leased=0 done=48 failed=0 cost=$0.400000 price/unit=0.045000already accepted: the job runs at this priceA bring-your-own job is quoted again when its units take more than twice as long on its run leases as in its calibration. A new price above the job’s price.max_per_unit, or one your uncommitted credit does not cover, pauses the job, and unitfabric job says which: nothing more of it runs until you accept the new price or cancel the job. unitfabric job then prints both ways on, each a line ready to paste. unitfabric accept <id> --price <p> agrees to the new price only if it is still p, the price unitfabric job showed you, and prints the job, running on at it; units already billed keep their price. The control plane accepts the price named or nothing: if the job was quoted again since, nothing is accepted and the CLI says so with the price waiting now. Above, the job was accepted elsewhere at 0.015000 after unitfabric job showed it, ran slower again and was quoted 0.045000, so the pasted line accepted nothing; unitfabric job then showed the new price and the line that accepts it. Without --price, unitfabric accept reads the job and accepts the new price waiting when you run it. Your uncommitted credit must cover the difference on every unit of the job (error 402 otherwise: top up, then accept again). An accept that finds the job already running at exactly the price it names (accepted a moment before from another terminal or the dashboard, or the same line pasted twice, as last above) prints the job and says already accepted: the job runs at this price, exit status 0, as the dashboard does. Any other job with no new price waiting is refused in the control plane’s words: job is running: it has no new quote to accept.
results
Section titled “results”unitfabric results job_9nww8dhegrj5 --out ./resultsDownloads every artifact of every done unit into ./results/<unit_id>/<artifact>, checks each file against its SHA-256, and prints downloaded <n> files (<m> already present) from <k> units into ./results. It is safe to run again: files already there with the right checksum are kept. The results come a page at a time (up to 1,000 units, fewer when the units hold many files: a page holds at most 20,000 of them), each page’s names are checked before any of its files is written, and nothing is written outside --out.
unitfabric keys listunitfabric keys create laptopunitfabric keys revoke key_...list shows each key’s id, name, prefix, creation time and revocation time. create prints the new key once; it is not shown again. revoke stops a key working at once and refuses an id that is not an active key.
Supplying machines
Section titled “Supplying machines”The commands of the supply side: an organization’s machines, the tokens that install them, what they earn and how it is paid out. They take the same login as the rest.
machines
Section titled “machines”$ unitfabric machinesid name gpu platform status rate/h trust start flags others units_today failed_today earned_todaynode_xkcjgi6h3x9m studio-4090 rtx4090 linux online 0.3000 50 50 pending yes 0 0 0.000000The organization’s machines, oldest first: GPU class (cpu for none), linux or wsl2, online or offline (silent for a minute), the rate in US dollars an hour, the trust and the trust it started at, and today’s units done, units failed and earnings (UTC). flags lists pending (not confirmed yet), paused, private and byo (accepts other organizations’ images), or -. others says whether other organizations’ jobs can reach the machine now: yes, or no: and why (paused, private, unbenchmarked, or unjudged: a class we cannot check yet, troubleshooting).
machines confirm
Section titled “machines confirm”$ unitfabric machines confirm node_xkcjgi6h3x9mid name gpu platform status rate/h trust start flags others units_today failed_today earned_todaynode_xkcjgi6h3x9m studio-4090 rtx4090 linux online 0.3000 50 50 - yes 0 0 0.000000A machine enrolled with one of the organization’s tokens takes none of its own jobs until it is confirmed here or on the dashboard’s Machines page.
machines set
Section titled “machines set”$ unitfabric machines set node_xkcjgi6h3x9m --rate 0.25id name gpu platform status rate/h trust start flags others units_today failed_today earned_todaynode_xkcjgi6h3x9m studio-4090 rtx4090 linux online 0.2500 50 50 - yes 0 0 0.000000--rate sets the rate (US dollars an hour, above 0, at most 1,000, four decimals at most), --name the name, --pause stops it taking work once it has finished what it holds and --resume starts it again, --private keeps it to the organization’s own jobs and --public opens it to others’, and --accept-byo lets it run other organizations’ own container images (pulled from their registries, which see the machine’s address) where --no-byo stops that. Each prints the machine as it is now; set with none of them is refused.
machines remove
Section titled “machines remove”$ unitfabric machines remove node_xkcjgi6h3x9mremoved: its token no longer works, and its work went back to the queueA removed machine does not come back: enrol it again as a new one. What it earned stays the organization’s, unless its trust was under the trust it started at (revocation).
supply tokens
Section titled “supply tokens”$ unitfabric supply tokens create studiocopy this token now; it is not shown again, and installs machines until 2026-11-01T18:17:02.160495+00:00:uf_supply_...install a machine with it (Linux, a user who may sudo): curl -fsSL https://unitfabric-site.vercel.app/install.sh | UNITFABRIC_SUPPLY_TOKEN=uf_supply_... sudo -E bash -s -- --control-url <url>Windows (an administrator PowerShell): $env:UNITFABRIC_SUPPLY_TOKEN = 'uf_supply_...'; & ([scriptblock]::Create((irm https://unitfabric-site.vercel.app/install.ps1))) -ControlUrl <url>Either line, as typed, stays in the shell's history with the token for the token's day: it enrols only machines you then confirm, and `unitfabric supply tokens revoke ID` ends it at once. To be asked for the token instead, which keeps it out of the history: curl -fsSL https://unitfabric-site.vercel.app/install.sh | sudo bash -s -- --control-url <url> & ([scriptblock]::Create((irm https://unitfabric-site.vercel.app/install.ps1))) -ControlUrl <url>Confirm each machine once it is installed: unitfabric machines confirm ID$ unitfabric supply tokensid name prefix created_at expires_at last_used_at revoked_atstok_6qwgxf8sut4u studio uf_supply_06005336 2026-10-31T18:17:02.160495+00:00 2026-11-01T18:17:02.160495+00:00 2026-10-31T18:17:02.167954+00:00$ unitfabric supply tokens revoke stok_6qwgxf8sut4urevoked: it enrols no machine from now on; the machines it enrolled run oncreate prints the token alone on stdout, uf_supply_ and 40 hex digits, and on stderr the two install lines with the token in them and the two that ask for it instead: the token is shown this once, enrols machines for a day, and stays that day in the history of a shell it was typed in. list (or no action) shows every token by its first 18 characters, with when it was made, when it stops enrolling, and when it last enrolled a machine. revoke stops a token enrolling at once; the machines it enrolled keep running. An organization has at most 20 tokens that have not expired or been revoked.
supply earnings
Section titled “supply earnings”$ unitfabric supply earningsearned $0.000417 unpaid $0.000417 (held $0.000417, credit only $0.000000) paid $0.000000 voided $0.000000 next payout 2026-11-05 (from $25.000000)created_at node_id job_id unit_id seconds amount_usd held_until credit_only paid voided2026-10-31T18:17:02.279044+00:00 node_xkcjgi6h3x9m job_i3c26pmg266j 000002 2.000 0.000139 2026-11-14T18:17:02.279044+00:00 False False False2026-10-31T18:17:02.264174+00:00 node_xkcjgi6h3x9m job_i3c26pmg266j 000001 2.000 0.000139 2026-11-14T18:17:02.264174+00:00 False False False2026-10-31T18:17:02.247867+00:00 node_xkcjgi6h3x9m job_i3c26pmg266j 000000 2.000 0.000139 2026-11-14T18:17:02.247867+00:00 False False False$ unitfabric supply earnings --daily 7day amount_usd units2026-10-31 0.000417 32026-10-30 0.000000 02026-10-29 0.000000 02026-10-28 0.000000 02026-10-27 0.000000 02026-10-26 0.000000 02026-10-25 0.000000 0The first line, on stderr, sums the organization’s earnings and says when the next payout runs; then the newest 200 earnings (--all pages through every one), each with the job as the machine was told it, the unit’s measured seconds, the amount, and held_until while a machine not yet verified has it held (how earnings work). --daily DAYS gives the totals of each of the last 1 to 366 days (UTC) instead.
supply rates
Section titled “supply rates”$ unitfabric supply ratesgpu_class rate_usd_per_houra10 0.4000a100 0.9000...rtx4090 0.3000...t4 0.1500any other class: $0.0125 an hour for each GB of its VRAMThe rate suggested to a machine of each GPU class, which it gets when it is enrolled without one; the pricing page lists them all.
supply payouts
Section titled “supply payouts”$ unitfabric supply payoutspayout account -: not set up: run `unitfabric supply onboard`created_at period amount_usd reversed_usd status stripe_transfer_id failureWhether the organization’s Stripe account can take payouts (ready, or set up and waiting for Stripe), then its payouts, newest first: the month, the amount, what a reversal took back of a paid one (how earnings work), and pending, paid with Stripe’s transfer id, or failed with Stripe’s reason.
supply onboard
Section titled “supply onboard”$ unitfabric supply onboardset up payouts at connect.stripe.com; open:https://connect.stripe.com/setup/e/acct_.../...Prints the Stripe page that sets up the organization’s payouts, or, once they are set up, its Stripe Express dashboard. --country is where the payee is, two letters (US by default), and counts the first time, when the account is made.