@bazantic/cli
v0.10.1
Published
Bazantic CLI · pay for and manage agent gateways (x402 · MPP).
Downloads
680
Readme
@bazantic/cli
CLI for the Bazantic substrate. The command is baz — what the examples below
spell and what the CLI's own --help prints. The longer bazantic and
bazantic-cli still work as aliases:
baz curl …— call any x402 endpoint and auto-settle the payment (USDC · Base).baz login/whoami/logout— sign in this device to manage gateways and Recipes. This session cannot spend.baz gateway …— register and list your gateways over the login session (requireslogin).baz wallet …— your self-custody wallet: the payer you hold and fund.baz grant …— authorize a device to pay from your Bazantic-hosted balance (capped, revocable).
Pay for API calls (x402)
baz curl calls any paid endpoint and settles the 402 for you. Every price,
cap and ceiling is USDC, never ETH and never a gas estimate. Payment settles on
Base (the default, over x402) or Tempo (over MPP). On Tempo the token is USDC.e,
and the tempo-moderato testnet uses AlphaUSD. You never handle a token unless
you want to.
Pay from a self-custody wallet you hold and fund (needs no Bazantic account), or
from your Bazantic-hosted balance via a capped, revocable grant.
# 1. Create a self-custody wallet (stored 0600; passphrase optional — press
# Enter to skip for unattended agent use, or set one to encrypt at rest)
baz wallet new
# → 0xYourAddress…
# 2. Fund it with USDC on Base, then check it landed.
baz wallet balance
# 3. Make a paid call — the 402 is signed and retried automatically
baz curl https://<provider>.bazgateway.com/<path> --account wallet --jsonPrefer to spend your Bazantic-hosted balance instead of a local wallet? Create a capped, revocable grant (approved once in the browser) and select it by name:
baz grant create --name agent-1 --cap 5 # authorize this device, 5 USDC cap
baz curl https://<provider>.bazgateway.com/<path> --account agent-1 --jsonA wallet holds funds you custody; a grant is a capped permission to spend your
hosted balance — they are different things. Because --account wallet always
means the wallet, wallet and local are reserved: grant create refuses them
as a --name rather than mint a grant that could never be selected.
curl flags
| Flag | Meaning |
| --- | --- |
| --account | who pays: a grant name, or wallet for the self-custody wallet (default: the sole grant, else the configured account) |
| -X, --method | HTTP method (default GET) |
| -d, --data | request body |
| -H, --header | extra request header (repeatable) |
| --max-amount | per-call spend ceiling in USDC (default 0.01) |
| --network | advanced: base | tempo (default: matches the gateway — base on production, base-sepolia on a preview; testnets: base-sepolia, tempo-moderato) |
| --x402-version | pin the accepted x402 version, 1 | 2 (default: the 402 body, then its PAYMENT-REQUIRED header) |
| --passphrase-stdin | read the local wallet passphrase from stdin |
| --yes | skip the pay confirmation (the ceiling still applies) |
| --json | structured result / errors |
| --verbose | print the settlement receipt to stderr |
Defaults in the config file
Set any of these once in the gateway block of ~/.bazantic/config.json. An
everyday call then needs no flags at all:
{"gateway": {"account": "my-grant", "maxAmountUsd": "0.05", "network": "base"}}account takes a grant name, or wallet for the self-custody wallet. A flag
always wins over the file. A stale account that names no credential on this
device is ignored, and the error lists the credentials you do have.
An --account you pass explicitly is checked against this device's credentials:
a name that matches none is an error listing the real ones, never a quiet
fallback to the wallet. A mistyped grant name would otherwise have moved the
spend from capped, revocable money to uncapped money without saying so. To pay
from the wallet, ask for it: --account wallet.
Environment (headless / CI / agents)
| Var | Purpose |
| --- | --- |
| BAZANTIC_GATEWAY_PRIVATE_KEY | use this key instead of the keystore |
| BAZANTIC_GATEWAY_PASSPHRASE | unlock the keystore without a prompt |
| BAZANTIC_GATEWAY_DEVICE_KEY | PEM key for a hosted-balance credential (headless single-credential use) |
| BAZANTIC_GATEWAY_RPC_URL | override the network RPC used by wallet balance |
For agent use (e.g. Claude Code), curl never prompts when stdin is not a TTY —
the --max-amount ceiling is the guard. Errors return one JSON object, e.g.
{"ok":false,"error":"amount_exceeds_ceiling","detail":…}, and the process exits
non-zero.
baz login --json and baz grant create --json both need a human to approve in
a browser. In --json mode they print the approval URL to stderr as one JSON
line ({"status":"pending_approval","verification_url":…}), and stdout keeps
only the final object. Neither opens a browser by itself in that mode. Pass
--browser when a script drives the command and a human is still at the
keyboard. --no-browser always wins over --browser.
Networks
USDC on Base is the default rail — with no --network, the CLI picks the
network from the gateway you're calling: a *.bazgateway.com gateway settles on
Base mainnet, a *.bazgateway.dev preview on Base Sepolia. A gateway in sandbox
mode answers with a 402 that offers Base Sepolia only, whatever its hostname is.
With no --network, baz curl then pays on Base Sepolia. A testnet holds no
real money, so the CLI asks for no confirmation. With an explicit --network
that the 402 does not offer, baz curl stops and names the network the 402
offered. Payments move real dollars, settled over x402. --network tempo
settles on Tempo instead, over MPP (Machine Payments
Protocol): the CLI signs a Tempo
transaction for the exact charge and the gateway broadcasts it only after your
call succeeds. On Tempo the token is USDC.e and fees come out of it — no gas
balance to manage. Both are production networks; you should never need to
think about which chain you're on unless you pass --network yourself.
For testing without real funds: --network base-sepolia (fund from the Circle
faucet, https://faucet.circle.com, selecting Base Sepolia) or
--network tempo-moderato (AlphaUSD on Tempo's Moderato testnet).
Spending your Bazantic-hosted balance uses a grant (baz grant create /
baz grant revoke) instead of a local wallet — see the pay examples above.
Grants settle on Base today, so Tempo payments need the self-custody wallet
(--account wallet).
Manage Recipes (login)
baz login creates a management session for the account that you select in the browser. New sessions include these scopes:
gateway:read
gateway:write
recipe:read
recipe:writeOld sessions can omit Recipe scopes. Run baz login again if a Recipe command reports a missing scope.
| Command | Purpose |
| --- | --- |
| baz recipe list [--json] | List Recipes that the selected account owns. |
| baz recipe get <handle> [--json] | Get one Recipe that the selected account owns. |
| baz recipe create <file> [--json] | Create a draft from a strict JSON file. |
| baz recipe update <handle> <file> [--json] | Update a draft from a strict partial JSON file. |
| baz recipe publish <handle> [--json] | Publish the current draft revision. |
| baz recipe unpublish <handle> [--json] | Return a published Recipe to draft state. |
| baz recipe delete <handle> [--yes] [--json] | Delete a draft after confirmation. |
List and get require recipe:read. All other commands in the table require recipe:write.
Recipe files
The create file contains exactly eight fields:
namedescriptioninput_schemainput_exampleoutput_exampleprompt_templatemodeltool_bindings
The update file contains a nonempty subset of the same fields. Unknown fields cause an error before a network request.
The compact UTF-8 JSON for a control definition cannot exceed 24 KiB. Update applies this limit after it merges the patch.
Neither file accepts a handle, owner, account, status, timestamp, or generation field. The API derives an immutable handle from name.
Each tool binding contains only gateway_slug and tool_name. A Recipe must have from 1 through 64 unique tool bindings.
prompt_template contains exactly one {{inputs}} placeholder. input_schema uses JSON Schema Draft 2020-12 and local references.
Supported model values are:
anthropic/claude-haiku-4.5
anthropic/claude-sonnet-4.6
anthropic/claude-opus-5
meta/llama-3.1-8b
deepseek/deepseek-v4-flash-0731
openai/gpt-5-nanoThis file is a valid create document:
{
"name": "Account Brief",
"description": "Summarize one account.",
"input_schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": { "account": { "type": "string" } },
"required": ["account"]
},
"input_example": { "account": "acme" },
"output_example": { "summary": "Example" },
"prompt_template": "Summarize {{inputs}}",
"model": "anthropic/claude-haiku-4.5",
"tool_bindings": [
{
"gateway_slug": "abcdefghijklmnopqrstuvwxyz",
"tool_name": "lookup_account"
}
]
}Create produces a draft. Update works only on a draft.
Publish reads bounded Recipe metadata for updated_at and validates the live tool bindings. A concurrent update causes a conflict.
Unpublish returns the Recipe to draft state. Delete works on a draft, so a published Recipe takes an unpublish first.
Interactive delete asks for confirmation. Noninteractive delete and --json delete require --yes.
The CLI has no Recipe transfer, compose, generate, test, or draft-run command. JSON success output uses an { "ok": true, ... } envelope.
Use hosted Recipes in MCP clients
First, configure a wallet or grant with the steps above. Set gateway.maxAmountUsd
to the maximum USDC amount for one Recipe call.
Register one dynamic Recipe MCP server in a supported client:
baz recipe install --client claude-code
baz recipe install --client codex
baz recipe install --client cursorRun only the command for your selected client. The installer does not install Claude Code, Codex, or Cursor.
The installer previews the bazantic-recipes entry before it changes the file.
The installer saves a 0600 backup of an existing config. The installer then uses an atomic write for the selected file.
| Client | User config |
| --- | --- |
| Claude Code | ~/.claude.json |
| Codex | ~/.codex/config.toml |
| Cursor | ~/.cursor/mcp.json |
The entry runs the current baz executable with the recipe mcp arguments.
It does not save individual Recipe tools. Each tools/list request gets the current published catalog.
The adapter sends initialize and public tools/list requests to the API /mcp endpoint. This endpoint needs no authentication for those methods.
If a valid CLI session exists, the adapter also connects to API /control-mcp. It adds authorized bazantic_ tools to each tools/list result.
The current Control MCP server exposes these tools:
bazantic_recipe_list
bazantic_recipe_get
bazantic_recipe_create
bazantic_recipe_update
bazantic_recipe_delete
bazantic_recipe_publish
bazantic_recipe_unpublish
bazantic_marketplace_search
bazantic_gateway_list_owned
bazantic_gateway_list_toolsThe three gateway discovery tools require gateway:read. Recipe read tools require recipe:read, and Recipe mutation tools require recipe:write.
bazantic_marketplace_search returns active, owner-published, Bazantic-verified services. bazantic_gateway_list_owned returns safe data for non-retired account gateways.
Both tools return at most four rows. Pass each next_offset value as offset until it is null.
Discovery rows omit descriptions, tags, URLs, timestamps, secrets, and raw specifications.
bazantic_gateway_list_tools makes a live request to one gateway. The account must own that gateway, or the verified marketplace must expose it.
The live result includes tool names, descriptions, input schemas, and annotations. Failure results use an explicit availability state instead of an empty tool list.
The adapter sends each bazantic_ call to API /control-mcp with the CLI session bearer. These calls do not load a wallet or use x402.
The adapter sends each published Recipe call to the paid Recipe gateway. The adapter pays through x402 with the configured payment source.
Without a valid CLI session, the adapter lists and calls public Recipes only. Public Recipe calls still use the paid gateway path.
The adapter does not use the generated gateway MCP endpoint. The direct route preserves the Recipe MCP request for the API.
Recipe calls use the account, network, and maxAmountUsd values from the gateway config block.
The stdio adapter does not request payment approval. It rejects a charge above maxAmountUsd.
In Production, Baz CLI uses https://api.bazantic.com/mcp. Production needs no Recipe endpoint environment variables.
During initialize, the canonical API advertises the direct Recipe execution URL. Baz CLI uses this URL for tools/call.
| Variable | Requirement | Purpose |
| --- | --- | --- |
| BAZANTIC_RECIPE_MCP_URL | Optional | Override the canonical API /mcp URL for local or Preview checks. The override API must advertise its direct execution route. |
Remove the Recipe MCP server
Remove only the bazantic-recipes entry from the selected client config:
- In Claude Code, remove
mcpServers.bazantic-recipesfrom~/.claude.json. - In Cursor, remove
mcpServers.bazantic-recipesfrom~/.cursor/mcp.json. - In Codex, remove
[mcp_servers.bazantic-recipes]from~/.codex/config.toml.
Also remove any Codex child table whose name starts with [mcp_servers.bazantic-recipes..
Keep all unrelated fields and MCP server entries. The install output gives the backup path for manual restore.
Manage gateways (login)
baz login signs this device in to manage your Bazantic account — create and
edit gateways. It is a control-plane session, completely separate from paying: the
token it stores can never spend or move funds (that is what wallet/grant are
for). Sign-in is a device-code flow — the CLI prints a URL, you approve it in the
browser, and the token lands in ~/.bazantic/config.json under a session block (0600).
# 1. Sign in (opens an approval link; approve it in your browser)
baz login
# → signed in as <account> · scopes gateway:read, gateway:write · until <date>
# 2. Register an API as a gateway. Bazantic fetches + parses the spec,
# generates the listing, and auto-generates its MCP endpoint.
baz gateway add \
--spec-url https://api.example.com/openapi.json \
--endpoint https://api.example.com \
--name "Example API"
# → gateway example-api
# mcp https://<gateway-host>/example-api/mcp
# 3. List your gateways, or sign out
baz gateway list
baz whoami # show the signed-in account + scopes + expiry
baz logout # end this device's session (also revocable from the dashboard)| Command | What it does |
| --- | --- |
| login | Device-code sign-in; stores a management session token (365-day, revocable). |
| whoami | Show the signed-in account, scopes, and expiry (--json for the block, never the token). |
| logout | Self-revoke the session server-side and clear the local token. |
| gateway add --spec-url <url> --endpoint <url> [--name <s>] [--auth-type <t>] [--status draft\|active] | Register a gateway under the session's account (default auth type: none); prints the slug + mcp_url. |
| gateway list | List the session account's gateways. |
| gateway resync <slug> [--yes] [--json] | Match the gateway's paid routes to the spec it stores. Prints which routes would be added, removed and kept, then applies the lot on one confirmation. A route in both keeps the price you set. --json prints the change and writes nothing. |
| gateway domains … | Manage a gateway's custom domain — see below. |
The account is chosen in the browser at approval (default: your personal account) — the CLI never holds a platform key, and a gateway is only ever created under an account you own.
Custom domains
Serve a gateway from your own hostname, such as api.your-domain.com, instead of its
*.bazgateway.com address. Bazantic verifies that you control the domain, issues a TLS
certificate for it, and then advertises that hostname as the gateway's URL. The original
URL keeps working.
Four steps take a hostname from nothing to live:
# 1. Claim the hostname. Bazantic mints a token and prints two DNS records.
baz gateway domains add my-gateway api.your-domain.com
# → hostname api.your-domain.com
# status pending_ownership
#
# Type Host Value
# TXT _baz-challenge.api.your-domain.com baz-verify=api.your-domain.com,<token>
# CNAME api.your-domain.com bazgateway.com
#
# then run: baz gateway domains verify my-gateway
# 2. Create both records at your DNS provider.
# TXT proves that you own the domain.
# CNAME sends the traffic to Bazantic.
# 3. Run the checks. DNS needs time to propagate, so a first failure is normal.
baz gateway domains verify my-gateway
# → hostname api.your-domain.com
# status pending_cert
# 4. Wait for the certificate. Bazantic re-checks on its own every few minutes.
baz gateway domains status my-gateway
# → hostname api.your-domain.com
# status active| Command | What it does |
| --- | --- |
| gateway domains add <slug> <hostname> | Claims a custom hostname for the gateway. It prints the TXT and CNAME records to create, and a hint to run verify next. API errors, such as hostname_in_use, print verbatim. |
| gateway domains status <slug> | Prints the hostname, the status, the state of each record, the failureReason when set, and lastCheckedAt. |
| gateway domains verify <slug> | Runs the DNS and certificate checks now, and prints the new status. A 429 prints the retry time. |
| gateway domains rm <slug> [--yes] | Releases the domain. It prompts first unless you pass --yes. It always warns that your own CNAME record stays in place. A dangling record is a subdomain-takeover risk. |
Every command takes --json.
Status values. pending_ownership waits for the TXT record. pending_dns waits for
the CNAME record, or for a CAA or AAAA problem to clear. pending_cert means the records
pass and the certificate is on order. active means the hostname serves traffic and
Bazantic advertises it. temporary_failure means a DNS lookup failed. degraded means
the hostname still serves but its DNS drifted. failed means the setup did not complete.
Failure reasons. A failed check reports one code. The CLI prints the code verbatim,
and the dashboard shows the same state in words. The common codes are txt_missing,
txt_mismatch, cname_missing, cname_wrong_target, cname_proxied,
caa_blocks_issuance, aaaa_conflict, registrar_autoappend_suspected,
dns_error_temporary, cert_pending, cert_failed, expired_pending, and dns_drift.
Limits. One custom domain for each gateway. Five for each account. Six verifications
an hour for each hostname. Ten domain changes an hour for each account. A setup that does
not reach active in 7 days expires.
Custom domains need a signed-in session (baz login) with gateway:write. They work
only where the webapp has the feature configured, which is production today. The full
walkthrough, including the certificate steps, is at
https://bazantic.com/docs/custom-domains.
Install
Published on npm as @bazantic/cli:
# Run without installing
npx -p @bazantic/cli baz --help
# Or install globally
npm i -g @bazantic/cli # or: pnpm add -g @bazantic/cliThe command is baz, with bazantic and bazantic-cli as aliases.
Update
baz update updates the CLI to the latest version. baz update --check
reports the latest version without an install. baz update --to <version>
moves to an exact version. Use --to to return from a bad release.
The CLI checks npm once per 24 hours, in a background process. The process
detaches from the command. No command waits on the network. When a newer
version exists, baz prints one line on stderr at exit.
| Opt-out | Effect |
| --- | --- |
| BAZANTIC_NO_UPDATE_CHECK | skip the check |
| NO_UPDATE_NOTIFIER | skip the check (shared convention) |
| GH_NO_UPDATE_NOTIFIER | skip the check (shared convention) |
| --no-update-notifier | skip the check for one command |
| CI | skip automatically |
| a pipe (stderr not a TTY) | skip automatically |
| --json | skip automatically |
baz update refuses to install under npx or inside a source checkout. A
global install there would shadow the checkout. Run npm i -g @bazantic/cli
for a global install. Run git pull inside a source checkout instead.
baz update --check still reports the latest version in both cases.
