Skip to content

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 --help
usage: 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 exit

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.

Terminal window
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.

$ unitfabric templates
id family list_price_usd unit_max_seconds
audio/whisper-large-v3@1 whisper 0.004000 600
image/flux-schnell@1 flux 0.004000 180
image/sdxl-turbo@1 sdxl 0.001500 120
render/blender@1 blender 0.020000 3600
text/embeddings@1 embeddings 0.001000 900
video/transcode@1 transcode 0.004000 600

Every template with its price per unit and its time limit per unit, in seconds. The pages under Templates describe each unit.

Terminal window
unitfabric account

Prints the organization’s id, name and balance as JSON.

Terminal window
unitfabric ledger
unitfabric ledger --all

The newest 200 credits and charges, with created_at, kind, amount_usd, job_id and reference; --all pages through every entry.

Terminal window
unitfabric topup 25

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

$ unitfabric upload take-01.mp4
take-01.mp4: 23.4 MiB of 23.4 MiB uploaded (100%)
r2://grid/uploads/org_ists4trnmppr/up_kw82s6htarhx/take-01.mp4

Uploads 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.mkv
unitfabric: 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 characters
$ unitfabric submit job.yaml
uploaded clips.jsonl as r2://grid/uploads/org_ists4trnmppr/up_25iq9pbxjfki/clips.jsonl
job_9nww8dhegrj5 queued units=2 price/unit=0.004000

Prints 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 jobs
id name status units_total done failed cost_usd price/unit
job_9nww8dhegrj5 first clips queued 2 0 0 0.000000 0.004000
job_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_9nww8dhegrj5
job_9nww8dhegrj5 queued pending=2 leased=0 done=0 failed=0 cost=$0.000000 price/unit=0.004000
$ unitfabric units job_9nww8dhegrj5
unit_id status attempts seconds price_usd failure_reason
take-01 pending 0
take-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).

$ unitfabric cancel job_9nww8dhegrj5
cancelled

Units that are not done fail with the reason cancelled, and nothing more of the job is billed.

$ unitfabric job job_b4xy9buq23dn
job_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.015000
unitfabric: 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_b4xy9buq23dn
job_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.045000
job_b4xy9buq23dn running pending=72 leased=0 done=48 failed=0 cost=$0.400000 price/unit=0.045000
$ unitfabric accept job_b4xy9buq23dn --price 0.045000
job_b4xy9buq23dn running pending=72 leased=0 done=48 failed=0 cost=$0.400000 price/unit=0.045000
already accepted: the job runs at this price

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

Terminal window
unitfabric results job_9nww8dhegrj5 --out ./results

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

Terminal window
unitfabric keys list
unitfabric keys create laptop
unitfabric 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.

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.

$ unitfabric machines
id name gpu platform status rate/h trust start flags others units_today failed_today earned_today
node_xkcjgi6h3x9m studio-4090 rtx4090 linux online 0.3000 50 50 pending yes 0 0 0.000000

The 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).

$ unitfabric machines confirm node_xkcjgi6h3x9m
id name gpu platform status rate/h trust start flags others units_today failed_today earned_today
node_xkcjgi6h3x9m studio-4090 rtx4090 linux online 0.3000 50 50 - yes 0 0 0.000000

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

$ unitfabric machines set node_xkcjgi6h3x9m --rate 0.25
id name gpu platform status rate/h trust start flags others units_today failed_today earned_today
node_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.

$ unitfabric machines remove node_xkcjgi6h3x9m
removed: its token no longer works, and its work went back to the queue

A 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).

$ unitfabric supply tokens create studio
copy 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 tokens
id name prefix created_at expires_at last_used_at revoked_at
stok_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_6qwgxf8sut4u
revoked: it enrols no machine from now on; the machines it enrolled run on

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

$ unitfabric supply earnings
earned $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 voided
2026-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 False
2026-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 False
2026-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 7
day amount_usd units
2026-10-31 0.000417 3
2026-10-30 0.000000 0
2026-10-29 0.000000 0
2026-10-28 0.000000 0
2026-10-27 0.000000 0
2026-10-26 0.000000 0
2026-10-25 0.000000 0

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

$ unitfabric supply rates
gpu_class rate_usd_per_hour
a10 0.4000
a100 0.9000
...
rtx4090 0.3000
...
t4 0.1500
any other class: $0.0125 an hour for each GB of its VRAM

The rate suggested to a machine of each GPU class, which it gets when it is enrolled without one; the pricing page lists them all.

$ unitfabric supply payouts
payout account -: not set up: run `unitfabric supply onboard`
created_at period amount_usd reversed_usd status stripe_transfer_id failure

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

$ unitfabric supply onboard
set 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.