CONTEXT FOR YOUR CODING AGENT

Meet Infimal.
Bring your agent.

One file with the model, the lifecycle, and the commands. Give your agent the context to build with Infimal.

infimal.mdMarkdown · no authentication required
Open raw ↗
01Give it the reference.

Paste the file into your agent’s context or let it fetch the raw URL.

02Describe your model.

Share its initialization, inference code, and the service you need.

03Review the plan.

Your agent defines the infrastructure. You stay in control of changes.

infimal.mdTHE FULL AGENT REFERENCE

Infimal

Infrastructure as code for model services with unpredictable demand.

Names and domains

Airglow is now infimal. The primary website is https://infimal.ai; the backup is
https://infimal.dev. API aliases are https://api.infimal.ai and https://ai.infimal.dev.
The CLI and the SDK default to https://api.infimal.ai; the API host from before the
rename remains supported. All aliases reach the same platform.
The native command is infimal, and it is the only CLI. The Python package is infimal
(import infimal); it installs no command, and the native command runs it for apps plan,
apply and deploy. The environment is INFIMAL_API_KEY, with INFIMAL_API_URL
for the native command and INFIMAL_ENDPOINT for the SDK. For one release the pre-rename
AIRGLOW_* and GLOW_* names are still read, with a warning; write the new names. Existing
API keys do not change. /infimal.md, /airglow.md and /agent.md serve this reference.
See /docs/migration/ for account and browser-session details.

Read this first

Python is the current authoring interface, not the core infrastructure format.
The platform contract is the serialized deployment specification. GPU selection belongs
to Infimal. Do not invent GPU SKU parameters or require a traffic forecast to get started.

The product is designed for scale to zero and scale out to thousands of replicas.
A configured maximum is a bound, not reserved capacity or a measured scaling guarantee.

Service configuration and request intent are separate. Access, scaling, and batching
belong to the service. Urgency and performance objectives belong to individual calls.
The current SDK still includes deployment-level perf/tier fields. /docs/model-syntax/
is the reference for the request surface that ships: every Client.model(id) method,
named exactly as the SDK implements it. It is not where a proposal is described.

Discover the documentation

- /docs/overview/ — product model and lifecycle
- /docs/quickstart/ — complete first-service workflow
- /docs/installation/ — source installation and environment configuration
- /docs/infrastructure/ — App, Endpoint, Scale, Batch and specification translation
- /docs/lifecycle/ — setup, snapshots, handlers and checkpoint boundaries
- /docs/scaling/ — replica bounds, queue age and residency policy
- /docs/batching/ — dynamic batching and streaming
- /docs/requests/ — request intent and priority contract
- /docs/deployments/ — plan, deploy, apply and verification
- /docs/observability/ — status, snapshots, logs and usage
- /docs/pricing/ — rates, accounting, held credit and funding
- /docs/access/ — endpoint visibility, key roles and API keys
- /docs/cli/ — exact current CLI surface
- /docs/troubleshooting/ — diagnostics by failing stage
- /docs/model-syntax/ — the shipped request syntax reference, by surface

Resolve these paths against the origin serving this document.

Install and configure

The native Rust infimal binary provides a terminal dashboard and scriptable commands.
Install on macOS or Linux (or WSL), then create a full-access CLI key at /cli/:

```sh
curl -fsSL https://infimal.ai/install.sh | sh
infimal login --key
infimal whoami
infimal
```

The login prompt is hidden. Never put a key in a command argument or URL.
INFIMAL_API_URL selects the API; INFIMAL_API_KEY overrides a saved credential.
Run infimal guide for setup help, or infimal tui --demo to explore sample data.

Python model authoring and requests use the SDK. infimal apps plan, apply and deploy
run your module through it, so install it where your module's dependencies are, or set
INFIMAL_PYTHON to that interpreter:

```sh
python -m pip install infimal
infimal apps plan --help
```

The Python SDK uses INFIMAL_ENDPOINT and INFIMAL_API_KEY; the native client uses
INFIMAL_API_URL and INFIMAL_API_KEY. The native installer does not install the SDK.

Call a model from Python

The request API ships in the SDK today:

```python
from infimal import Client

client = Client.from_env()
handle = client.model("MODEL_ID")   # an id from infimal models list --json
print(handle.chat("Explain this diff.").max_tokens(256).run().text)
caps = handle.capabilities()        # kinds, readiness and options for this listing
```

client.model(id) returns a handle. .chat(), .embeddings(), .speech(), .images() and
.video() each build one request; run() or stream() submits it once. Images and video
are jobs: .submit() returns a Job to wait(), refresh() or cancel(), run() waits for
you, client.job(id) adopts a job after a restart and client.jobs() lists them. Every
failure is an InfimalError carrying code, message, next and details; branch on code,
never on the message. InfimalUnreachable means nothing answered, so resubmit a job
with the same idempotency key rather than a new one.

Stack, Service, app.<model_type> and per-call bids are design proposals and are not
built: no code should call them, and /docs/model-syntax/ does not describe them —
it is the reference for the request surface that ships instead.

Infrastructure lifecycle

Apps are a preview and may change: the builder and gateway that would run a deployed App
are not live yet. The lifecycle and the commands below are the interface as it stands.

1. Declare an App with endpoint access, replica bounds, and batching.
2. Initialize reusable model state in @app.setup.
3. Infimal captures initialized process/model state at the snapshot boundary.
4. Handle each request with @app.handler, or each frame with @app.stream.
5. Review a plan, deploy or apply an authorized change, then inspect actual status.

```python
from infimal import App, Endpoint, Scale, Batch

app = App(
    "asr-emotion-checkpoint4200",
    endpoint=Endpoint(public=False),
    scale=Scale(min_replicas=0, max_replicas=1000),
    batch=Batch(max_size=16, window_ms=20),
)

@app.setup
def load():
    from my_model import load_model
    return load_model()

@app.handler
def transcribe(model, request):
    return model(request["audio"])
```

my_model is the customer's own module, not an installed Infimal package.
The handler's first argument receives the setup return value.
Do not preserve open sockets, pipes, or host file descriptors across setup.
A batched handler must return one answer per request, in the correct order.

Python model-authoring workflow

Run application commands from the directory containing the importable module.
Use a module name such as asr-emotion-checkpoint4200, not asr-emotion-checkpoint4200.py.

```sh
infimal apps plan asr-emotion-checkpoint4200
infimal apps deploy asr-emotion-checkpoint4200
infimal apps status asr-emotion-checkpoint4200
infimal apps snapshots asr-emotion-checkpoint4200
infimal apps logs asr-emotion-checkpoint4200 --limit 50
```

After editing a definition, inspect infimal apps plan asr-emotion-checkpoint4200, then run infimal apps apply asr-emotion-checkpoint4200.
Use --app NAME to select an App when a module declares more than one.
The --yes flag on apply skips confirmation; use only for an authorized workflow.
Plan imports user code. Inspect unfamiliar module-level side effects before running it.
Never infer success from command submission: inspect the resulting state.

Machine-readable commands

Prefer JSON for commands that support it:

```sh
infimal models list --json
infimal billing balance --json
infimal usage show --since 30d --json
infimal keys list --json
```

Every command supports --json, and every mutation a local --plan preview that sends
nothing. The full list, generated from the command manifest, is the last section here.

Access and financial actions

Start with Endpoint(public=False) unless public access is requested.
A key is either full or inference. Use inference for anything shipped inside a
client: it reaches the OpenAI surface, the model list and usage, and nothing else.
Key secrets are shown once at creation.
Revoke using the key ID returned by list, not its display name.

```sh
infimal keys create inference-client --role inference
infimal keys list --json
infimal keys revoke KEY_ID
```

Deploying, changing visibility, creating or revoking keys, and adding credit affect
external systems. Follow the user's authorized scope and the host agent's approval
rules. This reference does not authorize those actions on its own.

Credit enters an account two ways. infimal billing fund USD returns a Stripe checkout
link. After a lost response, inspect billing payments before retrying: the server does
not deduplicate checkouts. infimal billing referral --code-file PATH redeems a phrase
the operator handed out and is worth
one credit per account, ever: a second attempt answers 409 referral_already_used and
moves no money, so it is not a command to retry. Ten wrong phrases in an hour answer
429; do not guess a code you were not given.

Pricing and performance

Read the user's model rate from infimal models list --json. Do not use the landing-page
example rates as production prices. Do not promise universal savings, latency,
restore time, replica availability, or throughput from conceptual charts.
Idle inference has no processed work to meter; other charges must be confirmed
against the environment's actual terms. Available credit, held credit, and settled
usage are different accounting values.

Diagnose before retrying

- Missing key / 401: verify endpoint and credential injection without printing the key.
- Import error: verify module path, Python environment, and model dependencies.
- SnapshotBoundaryError: remove non-checkpointable setup state and inspect build logs.
- BatchShapeError: check one result per request and ordering.
- Waiting requests: inspect placement, residency, replica bounds, and capacity.

Website interaction boundary

The website is a static bundle that calls the control plane and the inference API on
their own origins. The landing page's SDK panel shows real Client.model(id) request
code, named exactly as the shipped SDK implements it; the panel itself does not run
anything. Sign-in is real (Google, GitHub, or a
six-digit emailed code) and creates or links the account's tenant. /glance, /billing,
/account and /terminal read the ledger, the key table, the deployments and the request
feed for the signed-in tenant; the catalogue and every price on the site come from
GET /v1/models. A panel whose data is unavailable says so and names why; a field the
platform does not record (a request's generated tokens, its latency) is labelled
"not recorded", never rendered as zero.

The browser terminal executes whoami, balance, usage, requests, keys, models, quote,
status, logs and chat against the live APIs; chat mints an inference key named
web-terminal for the browser. plan, deploy, apply, login, signup and install print the
CLI command and say the browser cannot run them: they need a source tree or a shell.
Do not treat those printed commands as executed. Top-up opens Stripe Checkout and the
credit lands when Stripe confirms the payment.

The optional filter_infimal_catalogue WebMCP tool on /explore/ filters the live
catalogue and returns real model ids, kinds, source kinds and USD prices. It provisions
nothing. Terms are at /terms/ and the privacy policy at /privacy/.

Command reference

Generated from the command manifest; infimal manifest --markdown prints it. Use --json for machine output. Every mutation accepts --plan for a local, redacted request preview. Plans are not server quotes.

### infimal models list

Browse available models and prices

`GET /v1/models`


### infimal models show

Inspect one listing: its price and what it accepts right now

`GET /v1/models/{id}`

The listing as models list shows it, with what its serving replicas reported beside it (D358): accepts_references, accepts_negative_prompt, reference_mode, video_timing, the LoRAs every replica holds, the default schedule, ready and the replica counts. A value no replica reported is null, never a guess. An unknown listing, one taken off sale and one you were not granted are the same 404 model_not_found.

- `ID`: Model listing ID (required)

### infimal models publish

Set visibility of a listing you own, or any listing as an administrator

`POST /v1/models/{id}/publish`

- `ID`: Model listing ID (required)
- `--public`: Public visibility (defaults to true; false makes private)

### infimal apps list

List your applications

`GET /v1/apps`


### infimal apps show

Inspect an application and its configuration

`GET /v1/apps/{app}`

- `APP`: Application name (required)

### infimal apps deploy

Build and place the App a Python module declares

`POST /v1/apps/{app}/deploy`

Needs Python 3.11+ with the infimal SDK installed: it runs your module.

Executes the module through the Python SDK (python -m infimal._synth), then posts the declaration and the source digest. A first deploy answers 409 build_queued with the build to poll. With --file, no Python runs: the file is the request body and --app names the app.

- `MODULE`: Python module that declares the App, as an import path (services.voice), not a file name (default main)
- `--app`: App name, when the module declares more than one
- `--file`: Send this JSON deploy request instead of executing a module; needs --app

### infimal apps config

Update an application's live configuration

`PUT /v1/apps/{app}/config`

- `APP`: Application name (required)
- `--file`: JSON request file; - reads stdin (required)

### infimal apps status

Inspect an application's placements

`GET /v1/apps/{app}/placements`

- `APP`: Application name (required)

### infimal apps logs

Read application build logs

`GET /v1/apps/{app}/logs`

- `APP`: Application name (required)
- `--limit`: Maximum number of rows
- `--build`: Build ID

### infimal apps snapshots

List published snapshots

`GET /v1/apps/{app}/snapshots`

- `APP`: Application name (required)

### infimal apps plan

Show what applying a Python module's App would change

`GET /v1/apps/{app}`

Needs Python 3.11+ with the infimal SDK installed: it runs your module.

Executes the module through the Python SDK (python -m infimal._synth), reads the live app and diffs the two: a change above the @app.setup line rebuilds the snapshot, one below it is configured in place and keeps it. Planning imports your code, so its imports must be safe to run.

- `MODULE`: Python module that declares the App, as an import path (services.voice), not a file name (default main)
- `--app`: App name, when the module declares more than one

### infimal apps apply

Apply a Python module's App: rebuild, or configure in place

`PUT /v1/apps/{app}/config`

Needs Python 3.11+ with the infimal SDK installed: it runs your module.

Plans first, then sends PUT /v1/apps/{app}/config for a change below the @app.setup line or POST /v1/apps/{app}/deploy for one above it. Asks before applying unless --yes.

- `MODULE`: Python module that declares the App, as an import path (services.voice), not a file name (default main)
- `--app`: App name, when the module declares more than one
- `--yes`: Apply without asking; required when stdin is not a terminal

### infimal builds show

Inspect a build's progress

`GET /v1/builds/{id}`

- `ID`: Resource ID (required)

### infimal gateway show

Inspect your applied gateway: its listings, their chains in order, its credential names

`GET /v1/gateway`

One row per source, in fallback order under its listing, with the credential it uses. Credentials are listed by name; a value is never returned. With none applied, the API answers 404 gateway_not_found.


### infimal gateway plan

Show what applying a Python module's Gateway would change, as the server sees it

`POST /v1/gateway/plan`

Needs Python 3.11+ with the infimal SDK installed: it runs your module.

Executes the module through the Python SDK (python -m infimal._synth) and sends what it declares to POST /v1/gateway/plan (D345, D418), which answers each listing, source, relay and credential as create, change, unchanged or delete, every refusal the apply would make, and warnings (a first-token SLA its fallback kinds never act on, a credential sealed long ago), and writes nothing. Reads no credential: the body names each credential's variable and carries no value. A first apply prints what the module declares above the plan.

- `MODULE`: Python module that declares the Gateway, as an import path (gateway), not a file name (default gateway)
- `--gateway`: Gateway namespace, when the module declares more than one

### infimal gateway apply

Apply a Python module's Gateway: its listings, chains and credentials, as one body

`PUT /v1/gateway`

Needs Python 3.11+ with the infimal SDK installed: it runs your module.

Plans first with POST /v1/gateway/plan (D418), which answers every change and every refusal and writes nothing; a plan with a refusal stops here, before any variable is read. With nothing to change it sends nothing, unless --reseal, which sends the unchanged declaration so a rotated value is re-sealed. Otherwise it reads the variable each declared credential names and refuses before sending if one is unset or empty, naming the variable and never a value. Then it asks, unless --yes, and sends the declaration with each credential's value to PUT /v1/gateway. --plan masks every key. Declared relays are applied around the PUT through /v1/sandbox-relays, as the plan lists them: creates by name (with each key read from its variable) and scales before it, deletes of named relays the declaration dropped after it; --reseal also re-sends every relay's keys. Relay instances are billed per second while they run, and the plan prints the rate. Nodes are declared in the platform file and rendered by infimal admin platform apply (D419).

- `MODULE`: Python module that declares the Gateway, as an import path (gateway), not a file name (default gateway)
- `--gateway`: Gateway namespace, when the module declares more than one
- `--yes`: Apply without asking; required when stdin is not a terminal
- `--reseal`: With no declaration change, send it anyway so every credential is re-sealed

### infimal keys list

List API keys without revealing their secrets

`GET /v1/keys`


### infimal keys create

Create an API key and print its secret once

`POST /v1/keys`

- `NAME`: Key name (required)
- `--role`: Key role (default: full)

### infimal keys revoke

Revoke an API key

`DELETE /v1/keys/{id}`

- `ID`: Resource ID (required)

### infimal billing balance

Show available credit and spending

`GET /v1/usage`

- `--since`: Time window: 30d, 12h, or 90m

### infimal billing fund

Create a checkout link to buy credit

`POST /v1/billing/checkout`

--wait polls your own payments (GET /v1/billing/payments) until this one is no longer pending: a completed row is credit the webhook has already put in the ledger. A wait that times out exits 1 and the payment is not lost.

- `USD`: Positive USD amount (up to six decimal places) (required)
- `--idempotency-key`: Reuse this key when retrying the same checkout; use a new key for a new purchase
- `--wait`: Block until the payment has landed and the credit is spendable
- `--timeout`: Seconds to wait for the payment (default 600)
- `--poll-interval`: Seconds between checks while waiting (default 3)

### infimal billing referral

Redeem a referral phrase once per account

`POST /v1/billing/referral`

- `--code-file`: File containing the referral phrase; - reads stdin (required)

### infimal billing payments

List credit purchases and receipts

`GET /v1/billing/payments`

- `--limit`: Maximum number of rows

### infimal usage show

Inspect spending by model

`GET /v1/usage`

--by model adds the per-model totals under client.by_model; the API's envelope is unchanged.

- `--since`: Time window: 30d, 12h, or 90m
- `--by`: Sum the spend per model, across its meters

### infimal usage series

Inspect spending over time

`GET /v1/usage/series`

- `--since`: Time window: 30d, 12h, or 90m
- `--bucket`: Time bucket

### infimal usage performance

See how your requests were served, per model

`GET /v1/usage/performance`

Per model you called, from your own served requests (D393): requests, the first-token time you received (admission to first byte) p50/p90/p99 and output tokens a second p50, in total and per bucket. Never a source or a provider: those are the listing owner's. Default window 30d.

- `--since`: Time window: 30d, 12h, or 90m
- `--bucket`: Time bucket

### infimal requests list

Inspect recent inference requests

`GET /v1/requests`

- `--limit`: Maximum number of rows

### infimal providers list

See your providers and sources, with the window's SLA summary

`GET /v1/providers`

Every provider your listings' sources dial, each source with its attempts, eligible attempts, success, first-token SLA verdicts (within, slow, timed_out, failed_fast), first-token and full-answer times and throughput, from the attempt log (D393). Default window 24h; quantiles here are estimates, the monthly report's are exact. 503 provider_data_unavailable on a deployment that keeps no attempt log.

- `--since`: Time window: 30d, 12h, or 90m
- `--tenant`: Admin tenants only: another owner's tenant ID, or platform for infimal's own sources

### infimal providers series

Chart each source's success, SLA and first-token time over time

`GET /v1/providers/series`

One point per source per bucket: attempts, eligible, success %, within-SLA %, first-token p50/p90/p99 and throughput p50. --since or --month, not both; the bucket is a day over three days or for a month, an hour otherwise.

- `--since`: Time window: 30d, 12h, or 90m
- `--month`: A UTC calendar month, YYYY-MM; not one that has not begun
- `--bucket`: Time bucket
- `--source`: One source's ID; default every source of every listing you own
- `--tenant`: Admin tenants only: another owner's tenant ID, or platform for infimal's own sources

### infimal providers attempts

List the attempts made against your sources, newest first

`GET /v1/providers/attempts`

One row per try against a provider, with its outcome, status, header, first-token and total times, tokens, the provider's own request and completion ids, and the SLA verdict and throughput the Rust rules give it. JSON pages up to 1000 rows (default 100) under an opaque next cursor; --format csv writes every row in scope to --out.

- `--since`: Time window: 30d, 12h, or 90m
- `--month`: A UTC calendar month, YYYY-MM; not one that has not begun
- `--source`: One source's ID; default every source of every listing you own
- `--outcome`: Only this outcome, e.g. timeout_first_token (docs/sla.md lists them)
- `--breaches`: Only breaches: missed the first-token SLA, or failed on the provider's side
- `--limit`: Maximum number of rows
- `--after`: The next cursor of the previous page
- `--format`: json pages; csv writes every row in scope to a file
- `--out`: Write the file here; default the name the API gives it, in the current directory
- `--tenant`: Admin tenants only: another owner's tenant ID, or platform for infimal's own sources

### infimal providers report

Produce a month's SLA report per source and provider

`GET /v1/providers/report`

Per source and rolled up per provider: eligible attempts, within / slow / timed_out / failed_fast against the bound each attempt ran under, within-SLA % and success %, exact first-token p50/p90/p95/p99/max, throughput p10/p50/p90, a row per day, the incidents (5-minute runs under 90% within or 95% success) and the log's coverage. Without --out it prints a table per source; with --out, or --format csv, it writes the API's file.

- `--month`: The UTC calendar month to report, YYYY-MM; the current one is reported as incomplete (required)
- `--source`: One source's ID; default every source of every listing you own
- `--format`: json (default) or csv
- `--out`: Write the file here; default the name the API gives it, in the current directory
- `--tenant`: Admin tenants only: another owner's tenant ID, or platform for infimal's own sources

### infimal providers breaches

Download a month's breach evidence for your provider

`GET /v1/providers/report/breaches`

Every eligible attempt of the month that was not within the bound, oldest first, with the provider's request id and completion id so the provider can match it line by line. Written to --out (default the API's file name); --format json without --out prints it.

- `--month`: The UTC calendar month to report, YYYY-MM; the current one is reported as incomplete (required)
- `--source`: One source's ID; default every source of every listing you own
- `--format`: csv (default, every breach) or json (at most 10000)
- `--out`: Write the file here; default the name the API gives it, in the current directory
- `--tenant`: Admin tenants only: another owner's tenant ID, or platform for infimal's own sources

### infimal hooks create

Create a git hook and print its secret once

`POST /v1/hooks`

- `--app`: Restrict the hook to an application

### infimal hooks revoke

Revoke a git hook

`DELETE /v1/hooks/{id}`

- `ID`: Resource ID (required)

### infimal uploads create

Reserve a tenant-owned reference image upload

`POST /v1/uploads`

- `--content-type`: image/png, image/jpeg or image/webp (required)
- `--bytes`: Exact byte length, within the API's upload limit (32 MiB by default) (required)

### infimal jobs submit

Submit an image or video generation

`POST /v1/images/generations`

Posts to /v1/images/generations or /v1/video/generations by --surface; the submit holds the credit and answers 202 with the job. Only the flags given are sent: the runner refuses a null. --wait polls GET /v1/jobs/{id}; a job that fails or is cancelled exits 1.

- `--surface`: Which endpoint: images are billed per image, video per generated second (required)
- `--model`: Model listing ID (required)
- `--prompt`: Prompt
- `--negative-prompt`: Negative prompt
- `--preset`: I1, I2, I4, V5 or V10: the workload table's geometry; explicit fields override it
- `--n`: Images: 1 to 4
- `--frames`: Video: frame count (V5 is 81, V10 is 161)
- `--fps`: Video: frames a second (16 for both presets)
- `--width`: Width in pixels
- `--height`: Height in pixels
- `--steps`: Sampling steps
- `--seed`: Seed
- `--adapter`: LoRA as id or id:weight
- `--reference`: A reference image (PNG, JPEG or WebP), sent as base64; repeatable up to the cap
- `--reference-key`: An uploaded reference key (uploads create); repeatable
- `--idempotency-key`: Resubmitting with this key returns the job you already have, not a second one
- `--wait`: Poll until the job reaches a state it will not leave
- `--timeout-s`: Seconds to wait before reporting what the job is doing (default 2700)

### infimal jobs get

Inspect one job: state, measured timings and artifact URLs

`GET /v1/jobs/{id}`

Artifact URLs are presigned and expire; read the job again for fresh ones. A job that failed or was cancelled exits 1.

- `ID`: Job ID (required)
- `--wait`: Poll until the job reaches a state it will not leave
- `--timeout-s`: Seconds to wait before reporting what the job is doing (default 2700)

### infimal jobs list

List your jobs, newest first

`GET /v1/jobs`

- `--limit`: Maximum number of rows

### infimal jobs cancel

Cancel a job that has not started, and be refunded

`DELETE /v1/jobs/{id}`

- `ID`: Job ID (required)

### infimal jobs download

Fetch a succeeded job's artifacts into a directory

`GET /v1/jobs/{id}`

Reads the job again for fresh presigned URLs and fetches each artifact without your API key: the URL is its own credential. A job that has not succeeded exits 1.

- `ID`: Job ID (required)
- `DIRECTORY`: Directory to write <index>.<ext> into (required)
- `--wait`: Poll until the job reaches a state it will not leave
- `--timeout-s`: Seconds to wait before reporting what the job is doing (default 2700)

### infimal video generate

Submit a video generation, billed per generated second

`POST /v1/video/generations`

The same submit as jobs submit --surface video. The API takes no duration: a clip is --frames / --fps seconds, or a preset's, and that length is what the hold and the charge are, never rounded. Only the flags given are sent: the runner refuses a null. --wait polls GET /v1/jobs/{id}; a job that fails or is cancelled exits 1.

- `--model`: Model listing ID (required)
- `--prompt`: Prompt
- `--negative-prompt`: Negative prompt
- `--preset`: V5 or V10: the workload table's clip; explicit fields override it
- `--frames`: Frame count (V5 is 81, V10 is 161)
- `--fps`: Frames a second (16 for both presets)
- `--width`: Width in pixels
- `--height`: Height in pixels
- `--steps`: Sampling steps
- `--seed`: Seed
- `--adapter`: LoRA as id or id:weight
- `--reference`: A reference image (PNG, JPEG or WebP), sent as base64; repeatable up to the cap
- `--reference-key`: An uploaded reference key (uploads create); repeatable
- `--idempotency-key`: Resubmitting with this key returns the job you already have, not a second one
- `--wait`: Poll until the job reaches a state it will not leave
- `--timeout-s`: Seconds to wait before reporting what the job is doing (default 2700)

### infimal sandbox-relays create

Store tenant Gemini keys or agy sign-ins and start relay instances

`POST /v1/sandbox-relays`

Keys are encrypted on the orchestrator volume. Instance limits and the tenant allowlist apply. With --name (1 to 64 of a-z, 0-9, '.', '_', '-'), an existing relay of that name is returned when its key labels, backend and models match, with changed values rotated and --instances ignored (scale it instead); anything else is 409 relay_conflict. --backend agy (D408) takes 1 to 16 --models and an agy sign-in per --env (AGY_TOKEN="$(scripts/agy-token-export.sh -)"). Each instance is billed per second while it runs, 60 s minimum. A gateway declaration that declares relays manages its named relays.

- `--env`: Key environment variable name (a Gemini key, or with --backend agy a sign-in); repeatable, never a key value
- `--instances`: Total instances across supplied keys, 0 to 32 (required)
- `--idempotency-key`: Stable key for retrying this create (required)
- `--name`: Relay name; same tenant and name is the same relay
- `--backend`: gemini (default) or agy: each --env then names a sign-in from scripts/agy-token-export.sh
- `--models`: With --backend agy: the agy model slugs the relay serves, comma-separated (a,b,c)

### infimal sandbox-relays list

List this tenant's relays

`GET /v1/sandbox-relays`


### infimal sandbox-relays get

Inspect a relay's desired and actual capacity

`GET /v1/sandbox-relays/{id}`

- `ID`: Sandbox ID (required)

### infimal sandbox-relays scale

Set a relay's desired instance count

`PATCH /v1/sandbox-relays/{id}`

- `ID`: Sandbox ID (required)
- `--instances`: Total desired instances; zero pauses the relay (required)

### infimal sandbox-relays delete

Stop a relay and remove its stored Gemini keys

`DELETE /v1/sandbox-relays/{id}`

- `ID`: Sandbox ID (required)

### infimal sandboxes create

Create a sandbox: an isolated Linux VM, billed by the second for its size

`POST /v1/sandboxes`

Full keys on enabled accounts only (403 sandboxes_not_enabled says how to ask). The create holds ten minutes at the size asked for; from Railway accepting it to its destroy it is billed by the second for that size, at least a minute (D398). It waits for running by default and exits 1 if the sandbox is not running when the wait ends; --no-wait answers at once. At most five sandboxes not ended per account (429 sandbox_limit).

- `--cpu`: vCPU, 0.25 to 4 in steps of 0.25 (default 1)
- `--memory-gb`: Memory in GB, 0.5 to 8 in steps of 0.5 (default 2)
- `--idle-timeout-minutes`: Minutes with no exec or keepalive before Railway ends it, 1 to 120 (default 10)
- `--ttl-seconds`: The longest it may live, 60 to 86400 seconds (default 3600)
- `--env`: A variable to set in the sandbox, by name: its value is read from your environment, never from an argument; repeatable
- `--wait`: Seconds the API holds its answer until the sandbox is running, 0 to 60 (default 60)
- `--no-wait`: Answer at once, while it is still being created (--wait 0)
- `--idempotency-key`: Creating again with this key returns the sandbox you already have, not a second one

### infimal sandboxes list

List your sandboxes, newest first; ended ones for seven days

`GET /v1/sandboxes`

- `--state`: Only sandboxes in this state
- `--limit`: Maximum number of rows

### infimal sandboxes get

Inspect one sandbox: its state, size, expiry and what it has cost

`GET /v1/sandboxes/{id}`

- `ID`: Sandbox ID (required)

### infimal sandboxes exec

Run a command in a running sandbox, and print what it wrote

`POST /v1/sandboxes/{id}/exec`

Prints the command's stdout on stdout and its stderr on stderr, and exits with its exit code; one that timed out or was killed exits 124. Railway keeps at most 16,000 bytes of each stream, and a cut is noted on stderr. --json prints the API's answer and still exits with the command's code. Longer work runs detached (nohup … &) and is read back with a second exec.

- `ID`: Sandbox ID (required)
- `COMMAND`: The command, run by the sandbox's shell: quote it as one argument (required)
- `--timeout-s`: Seconds before the command is stopped, 1 to 100 (default 60)

### infimal sandboxes keepalive

Reset a sandbox's idle timer

`POST /v1/sandboxes/{id}/keepalive`

- `ID`: Sandbox ID (required)

### infimal sandboxes delete

Destroy a sandbox; its billing stops when Railway confirms it gone

`DELETE /v1/sandboxes/{id}`

Answers the sandbox with its state: stopping until Railway confirms the destroy, then ended. Deleting one already stopping or ended answers it as it is.

- `ID`: Sandbox ID (required)

### infimal studio projects list

List your active Studio projects

`GET /v1/studio/projects`


### infimal studio projects create

Create an empty Studio project

`POST /v1/studio/projects`

- `--title`: Project title, 1 to 160 characters (required)

### infimal studio projects show

Read a Studio project: its title, document and revision

`GET /v1/studio/projects/{id}`

- `ID`: Studio project ID (required)

### infimal studio projects save

Replace a Studio project's title and document

`PATCH /v1/studio/projects/{id}`

The file is the body without its revision, {"title", "document": {"brief", "shots": [{"id", "title", "prompt", "picked_job"}]}}, and --revision is added to it. A project changed since that revision answers 409 and nothing is written; read it again. A picked_job must be a succeeded generation of that project and shot.

- `ID`: Studio project ID (required)
- `--file`: JSON {title, document} to save; - reads stdin (required)
- `--revision`: The revision studio projects show returned (required)

### infimal studio projects archive

Archive a Studio project; its jobs are kept

`DELETE /v1/studio/projects/{id}`

Soft: the project leaves the list, its generations stay attributable, and running work is not cancelled. A project changed since that revision answers 409.

- `ID`: Studio project ID (required)
- `--revision`: The revision studio projects show returned (required)

### infimal studio projects jobs

List a project's generations, newest first, with the shot each is for

`GET /v1/studio/projects/{id}/jobs`

- `ID`: Studio project ID (required)

### infimal studio projects generate

Generate a take of one shot from the shot's saved prompt

`POST /v1/studio/projects/{id}/generations`

Submits to /v1/images/generations or /v1/video/generations by --surface, with the saved shot's prompt in place of any prompt in --request. The job's idempotency key is studio:PROJECT:SHOT:REQUEST_ID, so a retry with the same --request-id returns the job already made, even after the project was edited; a new request against a changed project answers 409. Answers 202 with the job; follow it with infimal jobs get.

- `ID`: Studio project ID (required)
- `--shot`: Shot ID from the project's document (required)
- `--revision`: The revision studio projects show returned (required)
- `--request-id`: A UUID you choose; resending it returns the job it made, not a second one (required)
- `--surface`: Which endpoint: images are billed per image, video per generated second (required)
- `--request`: The generation body without its prompt, as JSON or @file, e.g. {"model": "…", "preset": "I1"} (required)

### infimal admin models list

List the administrator model catalogue

`GET /v1/admin/models`


### infimal admin models set

Create or update a listing from a JSON body

`PUT /v1/admin/models/{id}`

- `ID`: Model listing ID (required)
- `--file`: JSON request file; - reads stdin (required)

### infimal admin models create

Create a private, flat-priced media listing and grant it, in one step

`PUT /v1/admin/models/{id}`

A job listing needs its surface, a pinned flat price, private visibility and a grant for every tenant that submits to it; this sets all four (PUT /v1/admin/models/{id}, then PUT /v1/admin/grants per --grant). Running it again changes nothing.

- `ID`: Model listing ID (required)
- `--kind`: The surface the listing serves (required)
- `--flat`: USD per unit: per image on images, per second of encoded video on video (required)
- `--grant`: Tenant ID to grant; repeatable
- `--display-name`: Display name
- `--public`: List it for every tenant (default private)

### infimal admin models set-price

Set the one price a model is billed at

`PUT /v1/admin/models/{id}`

Give --input and --output together, or --flat, or --unpriced: an input price with no output price would bill output tokens at zero. A price set here is pinned unless --unpin.

- `ID`: Model listing ID (required)
- `--input`: USD per million input tokens
- `--output`: USD per million output tokens
- `--cache-read`: USD per million cache-read tokens
- `--flat`: USD per request, for image and speech surfaces
- `--unpriced`: Take it off sale; the router refuses it
- `--unpin`: Let the five-minute refresher move this price again

### infimal admin models set-image

Point a listing at the image that serves it, or clear it with none

`PUT /v1/admin/models/{id}`

The API parses the image id and refuses a malformed one; none clears the pointer, which is what a listing bought from a provider carries.

- `ID`: Model listing ID (required)
- `IMAGE`: gi:blake3:<64 hex> (the bare hex is accepted), or none (required)

### infimal admin models publish

Set any listing's visibility

`POST /v1/models/{id}/publish`

- `ID`: Model listing ID (required)
- `--public`: Public visibility (defaults to true; false makes private)

### infimal admin platform show

Inspect the platform declaration: listings, shares and shared resources

`GET /v1/admin/platform`


### infimal admin platform plan

Validate and diff a Platform declaration

`POST /v1/admin/platform/plan`

Needs Python 3.11+ with the infimal SDK installed: it runs your module.

Executes the Python declaration and asks the API for a read-only plan of its listings, shares, shared resources and the nodes it would render (D419): each change, every refusal and warnings such as an unpriced slot class. Contains no credential values.

- `--module`: Python module declaring one Platform; default platform_config

### infimal admin platform apply

Render the platform's nodes, then price, publish and share its listings

`PUT /v1/admin/platform`

Needs Python 3.11+ with the infimal SDK installed: it runs your module.

Plans first; a plan with a refusal stops here. Each node the plan creates is rendered with POST /v1/admin/nodes and its script written to --out <dir>/<node>.sh (mode 600; the one-use credentials are in it); a node whose body changed is refused unless --rerender, because a node is re-rendered, never edited in place. Then it atomically replaces listings, shares and publications at the planned version. A listing the declaration drops goes off sale; a share it drops is revoked. --plan sends no write.

- `--module`: Python module declaring one Platform; default platform_config
- `--yes`: Apply without asking
- `--out`: Directory each node's rendered bootstrap script is written to, as <node>.sh
- `--rerender`: Render a new bootstrap script for a node whose body changed

### infimal resources list

List shared resources granted to you

`GET /v1/resources`


### infimal resources show

Inspect a shared resource's model aliases

`GET /v1/resources/{name}`

- `NAME`: Published resource name (required)

### infimal admin sources list

List model sources and their health

`GET /v1/admin/sources`


### infimal admin sources add

Add a provider a model is bought from

`POST /v1/admin/sources`

Give --cost-input and --cost-output together, or --cost-flat. The key is sealed under INFIMAL_MASTER_KEY and cannot be read back. A `jobs` object in --wire-config makes the source a venue for its listing's video jobs, placed and collected by infimal-api with this key: {"jobs": {"dialect": "openrouter"}}, with optional concurrency (1), spill_after_s (unset: never while an own replica serves the listing) and max_resolution (720p).

- `--model`: Model listing ID (required)
- `--name`: Unique source name; the credential is sealed against it (required)
- `--base-url`: The provider's API base URL (required)
- `--upstream-model`: The id the provider knows this model by
- `--key-env`: Environment variable holding the upstream key, never the key itself (required)
- `--cost-input`: USD per million input tokens the provider charges
- `--cost-output`: USD per million output tokens the provider charges
- `--cost-flat`: USD per request the provider charges
- `--wire-config`: Wire configuration: JSON, or @file
- `--priority`: Higher is tried first

### infimal admin sources set

Update a source from a JSON file

`PUT /v1/admin/sources/{id}`

- `ID`: Resource ID (required)
- `--file`: JSON request file; - reads stdin (required)

### infimal admin sources rekey

Replace a source credential

`POST /v1/admin/sources/{id}/rekey`

- `ID`: Resource ID (required)
- `--key-env`: Environment variable holding the new upstream key (required)

### infimal admin sources delete

Delete a model source

`DELETE /v1/admin/sources/{id}`

- `ID`: Resource ID (required)

### infimal admin grants set

Grant or revoke a tenant's access to a model

`PUT /v1/admin/grants`

- `TENANT`: Tenant ID (required)
- `MODEL`: Model ID (required)
- `--granted`: true grants access; false revokes it (required)

### infimal admin tenants show

Inspect a tenant: balance, keys and terms

`GET /v1/admin/tenants/{id}`

- `ID`: Resource ID (required)

### infimal admin tenants financial

Inspect bounded grant accounting and reconciliation status

`GET /v1/admin/tenants/{id}/financial`

- `ID`: Resource ID (required)
- `--grant`: Optional grant ID for canonical outcomes
- `--after`: Outcome page cursor; requires grant
- `--after-unknown`: Unknown-reservation cursor; requires grant
- `--limit`: Page size (1..100; default 25)

### infimal admin tenants set-discount

Set a tenant's discount in basis points

`PUT /v1/admin/tenants/{id}`

- `ID`: Resource ID (required)
- `BPS`: Discount in basis points (0..10000) (required)

### infimal admin financial policy

Read the durable fleet credit-risk policy

`GET /v1/admin/financial/policy`


### infimal admin financial set-policy

Explicitly configure new-grant admission and the fleet risk cap

`PATCH /v1/admin/financial/policy`

- `--enabled`: Allow new grants; does not change existing allocations (required)
- `--cap-micros`: Fleet risk cap in micro-USD (0..5000000; cannot fall below allocated risk) (required)

### infimal admin topup

Credit a tenant using a unique payment reference

`POST /v1/admin/tenants/{id}/topup`

- `TENANT`: Tenant ID (required)
- `USD`: Positive USD amount (up to six decimal places) (required)
- `--reference`: Stable payment reference; reuse only for the same payment (required)

### infimal admin audit

Inspect administrative audit events

`GET /v1/admin/audit`

- `--limit`: Maximum number of rows

### infimal admin nodes list

List enrolled GPU nodes and what each serves

`GET /v1/admin/nodes`

A replica still loading its weights is listed as not ready; the provider instance id says which machine to terminate.


### infimal admin nodes bootstrap

Mint a rented box's one-use credentials and print its bootstrap script

`POST /v1/admin/nodes`

Give --image (per image, D359) or --engine (per GPU), not both. The script alone goes to stdout and the notice to stderr, so `infimal admin nodes bootstrap … > bootstrap.sh` is a runnable file; run it on the box as root. It carries a one-use enrolment token and a one-use tailnet key and is the only copy of either. The --engine grammar is comma-separated key=value: gpu, surface (images|video), model (the listing) and checkpoint (what the runner loads) are required; loras_file=<registry.json> (read here and sent inline), lora_registry=<path on the box>, default_loras=a:1+b:0.9, storage_dtype, offload=true|false, backend=wan|diffusers|sglang and sglang_config=<path> are optional.

- `--node-id`: What to call the box; it joins as node-<id> (required)
- `--region`: Region (default us-west-1)
- `--slot-class`: A key of deploy/fleet/slot-classes.json, e.g. lambda/gpu_8x_a100_80gb_sxm4 (required)
- `--gpus`: GPUs on the box (required)
- `--image`: Per image (D359): one image or a list, as JSON or @file; repeatable
- `--engine`: Per GPU (the older form): gpu=0,surface=images,model=<listing>,checkpoint=<hub id>[,…]; repeatable
- `--residency`: How the node parks (per-image form); the node's default is checkpoint
- `--cuda-checkpoint`: The node's cuda-checkpoint: a path on the box, or {"url", "sha256"} as JSON or @file
- `--provider`: Who the box was rented from; the API assumes lambda when absent
- `--provider-instance-id`: The provider's id for this instance, so nodes list says which one to terminate
- `--torch-index-url`: The CUDA torch index the runners install from (default: cu128)
- `--vllm-version`: The exact vLLM a chat image runs on, e.g. 0.32.0; required with a chat image (D409)
- `--image-id`: Materialise this image from the librarian instead of pulling from the Hub
- `--hf-token`: Ask for the fleet's Hugging Face token; only for gated weights on the Hub path

### infimal admin nodes reconcile

Render a node's declared body again for its box to apply in place

`POST /v1/admin/nodes/{node_id}/reconcile`

D423. Re-renders the bootstrap of the node's newest declared body (the one POST /v1/admin/nodes recorded, D419) with new one-use credentials. The script alone goes to stdout and the two steps to stderr: copy it to the box, run it as root; the node reconciles its adopt manifest in place (D422). --plan mints nothing: it compares the adopt manifest the box reports running with the one the declared body renders and prints both digests.

- `NODE`: Node ID (required)

### infimal admin nodes retire

Stop placing on a node; does not terminate it

`DELETE /v1/admin/nodes/{node_id}`

- `NODE`: Node ID (required)

### infimal admin nodes logs

Read a GPU node's journal: its engines, the node and the stager

`GET /v1/admin/nodes/{node_id}/logs`

D424. The node streams its own journal over the tailnet and the api relays it to you, so no ssh is needed: `infimal admin nodes logs b200-0 --unit engines --since 1h`, or `--follow` to watch a box come up. Lines are journalctl's, UTC, with each engine named infimal-engine-<image>-gpu<N>; the runner's own lines are JSON. A read stops at 16 MiB or 30 s and a follow at 64 MiB or ten minutes, with a last line saying which.

- `NODE`: Node ID (required)
- `--unit`: all (the default), node, stage, engines, a replica id, or infimal-engine@<image>-gpu<N>
- `--since`: How far back: 30s, 15m, 1h, 2d, or an RFC 3339 time
- `--lines`: The last N lines (200 by default, at most 10000)
- `--follow`: Keep printing lines as they are written; ends after ten minutes
- `--format`: text (the default), or journald's JSON, one entry a line
- `--out`: Write the lines to this file instead of standard output
Looking for the human-readable guides?Explore the documentation