API

API

The ix API is a binary RPC protocol, not HTTP with JSON. The CLI and the TypeScript, Python and Rust SDKs speak it, so use one of them.

Address and transport

  • Base URL: https://api.ix.dev. Set IX_SERVER to use another one.
  • RPC endpoint: https://api.ix.dev/rpc. The client appends rpc to the base URL.
  • Transport: WebTransport, which is HTTP/3 over QUIC. Each call opens one stream. The call name travels in the x-ix-method request header, and the body is a binary frame.
  • Browsers: @indexable/sdk/browser uses the native WebTransport API. Node and Bun have no WebTransport, so their SDK is a native addon that runs the same client in Rust.

Authentication

The CLI and the SDKs send your credential as a bearer token in the Authorization header and find it in this order:

  • An explicit argument, for example new Client({ token }) or ix_sdk.Client(token="...").
  • The IX_TOKEN environment variable: an API key, for CI and servers.
  • The login ix login stored in ~/.config/ix/config.toml.

If none is found the SDK raises Unauthorized with the text run `ix login`.

auth.sh
ix login                              # a person, once
ix me                                 # confirms who you are

ix keys create my-agent --limit 25    # CI or a server: prints a key once
export IX_TOKEN="the-key"

Each key can carry a spending ceiling in dollars (--limit) and permission scopes (--scope vm:*). A key cannot grant more than it holds. See secrets and keys.

SDK clients

clients.ts
// Node and Bun. Reads IX_TOKEN, then ~/.config/ix/config.toml.
import { Client, Machine } from "@indexable/sdk"
const client = new Client()                 // or new Client({ token, baseUrl })

// Browser. WebTransport only.
import { Client as BrowserClient } from "@indexable/sdk/browser"
const web = new BrowserClient({ token, baseUrl: "https://api.ix.dev" })

Operations

Python uses the TypeScript names in snake case. The wire method is the value of x-ix-method.

OperationCLITypeScriptWire method
Create a machineix new [image]Machine.create(options), client.machines.create(options)vm.build_commit
List machinesix lsMachine.list()vm.list
Find a machine by nameix lsMachine.attach(name)vm.get_by_name
Run a commandix shell <vm> --noninteractive -- <cmd>machine.exec([...]), machine.exec([...], { check: true })vm.exec
Read, write and list filesnonemachine.readFile, writeFile, listDirvm.fs.read, vm.fs.write, vm.fs.list
Take a snapshotix snapshot create <vm>machine.snapshot()vm.snapshot
List snapshotsix snapshot ls <vm>client.snapshots.list(machineId)vm.vcs.list_snapshots
Restore a snapshotix new <snapshot-id>client.snapshots.restore(id, name)vm.vcs.restore
Start, stop, restartix start, ix stop, ix restartmachine.start(), stop(), restart()vm.start, vm.stop, vm.restart
Remove a machineix rm <vm>machine.delete()vm.delete
Read logsix logs <vm>machine.logs(), machine.tailLogs()vm.logs, vm.logs_tail
Reach a portix new --l7-proxy-port <port> publishes a portmachine.forwardPort(port), machine.connectPort(port)vm.port_forward
Store a secretix secret set <name>client.secrets.set(name, value)secret.set
Create an API keyix keys create <name>client.keys.create(name, limitUsd)billing.create_token
Create a network groupix group create <slug>client.groups.create(slug)group.create

Run ix <command> --help for the flags of each command.

Errors

A call that fails returns a typed error with a code, such as Unauthorized, NotFound, PermissionDenied, Conflict or a payment error when the balance is empty. A non-zero exit code from a command is a result of exec, not an error. Pass check: true to turn it into one.

esc
  • Loading