<!--
Sitemap:
- [PayBox](/index): The non-custodial wallet for AI agents
- [Getting started](/getting-started)
- [Credentials & agents](/concepts/model): The PayBox model
- [Approvals & passkeys](/concepts/approvals)
- [Request lifecycle](/concepts/requests): From intent to result
- [MCP connector](/connect/mcp)
- [OAuth 2.1](/connect/oauth)
- [MCP tools](/reference/mcp-tools): What an agent can call
- [API & endpoints](/api-reference)
- [SDK & CLI](/sdk-cli)
-->

# SDK & CLI

`@paybox-sh/sdk` is a typed Node SDK and `paybox` CLI for the PayBox agent API.
It wraps the core MCP tool surface — `list_credentials`, `request_payment`,
`claim_payment_credentials`, `request_wallet_sign`, `request_secret`,
`request_swap`, `get_portfolio`, `verify_solana_balance`, `get_buy_link`,
`request_account_change`, the x402 tools (`discover_services`, `pay_x402`,
`use_service`), `get_request`, and
REST-supported read-only [plugin tools](/reference/mcp-tools#plugin-tools) — but
talks to the REST API directly, with no MCP host and **no signing iframe**.
Wallet signing is non-custodial and runs in-process: the SDK reuses the
wallet-sign signing core and signs the MoonX envelope with your `pbxk1.` key;
the MoonX secret never reaches the client.

:::note
A few surfaces are **MCP-only today**: geofenced World money tools; request history
([`list_requests`](/reference/mcp-tools#list_requests)) has no SDK method or CLI
command, and the wrappers don't expose the advanced x402 options (the v2
`x402_version` + `resource` fields on `pay_x402`; custom `headers` and probe
mode on `use_service`). Use the [MCP connector](/connect/mcp) for those.
:::

The request/response types are generated from the server's MCP `list_tools`
schemas, so the SDK stays in sync with the API rather than being hand-maintained.

## Install

Use the CLI when the agent runs on **Grok Bot**, **Grok Computer**, or any
computer/VM with no connector screen. Use the [MCP connector](/connect/mcp) when
the host has one (Claude.ai, Grok at `grok.com`, ChatGPT web).

```bash
pnpm add @paybox-sh/sdk      # library
npx @paybox-sh/sdk login     # or use the CLI directly
```

## Library

```ts
import { PayboxClient } from "@paybox-sh/sdk";

// Reads ~/.config/paybox + env (PAYBOX_API_KEY / PAYBOX_API_URL), or pass
// { apiKey, signingKey } / { token } explicitly.
const paybox = PayboxClient.fromConfig();

const { credentials, ungranted_summary } = await paybox.listCredentials();
const pay = await paybox.requestPayment({
  credentialId: credentials[0].credential.id,
  merchant: "Acme",
  merchantUrl: "https://acme.com",
  amountCents: 1999,
});

// Count-only hints; no ungranted credential IDs, names, addresses, or metadata.
console.log(ungranted_summary.wallet.evm);

const signed = await paybox.requestWalletSign({
  credentialId: walletId,
  intent: { op: "message", message: "gm" },
}); // signs in-process when a pbxk1. signing key is configured

const verified = await paybox.verifySolanaBalance({
  address: "5EUa…SViS",
  tokenMint: "EPjF…Dt1v",
  transactionSignature: "5h6x…q2Ms",
});
console.log(verified.balance.ui_amount_string);

// Approval-mode operations can wait for the user's passkey approval and then
// sign in-process — the headless equivalent of the in-chat signing window.
const swap = await paybox.requestSwap(
  {
    credentialId: walletId,
    srcChain: "eip155:8453",
    srcToken: "native",
    dstToken: "0x…",
    amount: "1000000000000000",
  },
  { waitForApproval: true },
);
```

## CLI

```bash
paybox login                              # OAuth 2.1 + PKCE, then provision a key
paybox login --no-provision               # OAuth only, skip the key step
paybox login --key pbx_live_…             # personal API key instead (no OAuth)
paybox login --signing-key pbxk1.…        # supply the signing key non-interactively

paybox credentials                        # list usable credentials
paybox pay --credential <id> --merchant Acme --url https://acme.com --amount 1999
paybox claim <request_id>                 # claim an approved payment's one-time card
paybox secret --credential <id> --purpose "deploy"
paybox sign --credential <id> --intent '{"op":"message","message":"gm"}'
paybox swap --credential <id> --src-chain eip155:8453 --src-token native --dst-token 0x… --amount 1000000000000000
paybox portfolio --address 0x… --networks 1,8453
paybox verify-solana-balance --address 5EUa…SViS --mint EPjF…Dt1v --transaction 5h6x…q2Ms
paybox buy-link --credential <id> --amount-usd 50   # MoonPay checkout URL to fund a wallet

paybox discover "weather api"             # browse curated paid x402 services
paybox pay-x402 --credential <id> --url https://svc/api --accepts @accepts.json
paybox use-service --credential <id> --url https://svc/api   # paybox pays + fetches

paybox account-change --note "need a wallet grant"  # link for the user to update access
paybox request <request_id> --wait        # poll a pending request

paybox version                            # installed version (+ update check)
paybox update                             # update the global install to latest
paybox uninstall                          # clear stored config (prints npm rm)

paybox --json credentials                 # machine-readable output on any command
```

REST-supported read-only plugin tools get their own command groups (today
`world` and `hyperliquid`; run `paybox --help`, then `paybox <plugin> --help`).
If the plugin isn't enabled, the command errors with the plugin to enable.
Official plugins without a CLI group are not a missing flag to invent: use MCP
[`discover_plugins`](/reference/mcp-tools#plugin-tools) (`status: enabled`, no
`query`) → `get_contract` → `use_plugin`.

### Logging in

`paybox login` is a guided two-step:

1. **Authenticate** — starts an OAuth 2.1 **device-code** grant. The CLI prints a
   verification URL and a short code, tries to open that URL locally, and polls
   until you approve with a passkey. Because it's device-code, the browser that
   approves doesn't have to be on the machine running the CLI: you can open the
   printed URL on your phone, or on a laptop while the CLI runs on a server. A
   scoped, audience-bound token (and a refresh token) is saved to
   `~/.config/paybox/config.json` (mode `0600`). If the code expires before you
   approve, run `paybox login` again for a fresh one.
2. **Provision a signing key** — to sign or swap, the CLI then opens the PayBox
   app's signing-key page for this agent, where a `pbxk1.` key is minted scoped
   to the wallets you granted. Paste it back at the prompt and it's stored
   locally. The key never leaves your machine except to MoonX; PayBox never sees
   it. Pass `--no-provision` to skip, or `--signing-key <pbxk1.…>` to supply it
   directly.

Prefer the interactive prompt over `--signing-key` when something else is driving
the CLI: a value passed as an argument lands in shell history and is visible to
anyone who can list processes.

Running two environments from one machine? Point `PAYBOX_CONFIG_DIR` at a
separate directory per environment. `login` rewrites the auth block of the config
it's pointed at, so sharing one directory across environments will overwrite the
credentials you already had there.

### When an agent is driving

The section above assumes you are at the terminal. Often you are not: the CLI
runs on a box, and you are in a chat with the agent. Four things change.

**The link is the agent's job.** `paybox login` writes the verification URL and
the `Code:` line to stderr, and tries to open a browser on its own machine. That
open is useless when the machine is not yours. The agent has to read those two
lines and put the URL in front of you as something you can tap. If it does not,
login polls until it times out and nothing tells you why. On a phone those chat
links should open the PayBox app. After you approve Connect CLI, the app opens
Generate a signing key. The signing-key URL's `client_id` is the access token
`cid` (the vault agent), never the OAuth client id that starts with `pbx-oauth-`.

**Nothing else should reach the chat.** From the install to a working session you
need exactly two things: that link, and a masked field to paste the `pbxk1.` key
into. Progress narration in between is noise the agent is adding.

**Nothing is attached to stdin.** The key prompt reads from a terminal that is
not there, so the agent should set `PAYBOX_SIGNING_KEY` in the environment before
running `login` instead. Never `--signing-key <value>` on the command line: an
argument lands in shell history and is visible to anyone who can list processes.

**Setup is not repeatable.** If `paybox --json whoami` already reports
`canSign: true`, the work is done. Running `login` again sends you a second
authorize link, which reads as the first one having failed.

`@paybox-sh/sdk` ships a `SKILL.md` with this same flow written as instructions
for the agent rather than for you. It is inside the package, at
`node_modules/@paybox-sh/sdk/SKILL.md` after an install, and on the package's
[npm page](https://www.npmjs.com/package/@paybox-sh/sdk). Point your agent at it
and it will not need any of this page.

### Signing and approvals

`sign`, `swap`, `pay-x402`, and `use-service` complete in-process when a
`pbxk1.` signing key is configured (`paybox whoami` shows `canSign`) and the
operation clears immediately under an autonomous grant. Without a key those
commands stop at `pending_signature`.

When a grant requires approval, pass `--wait`: the command waits for your
passkey approval in the app, signs in-process, and then keeps polling until the
request reaches a terminal state — there is no signing window on this surface.
Without `--wait` you get the first snapshot back instead, which may still be
settling (`pending_confirmation`), so poll `paybox request <id>` yourself before
treating the operation as finished. Solana swaps are safe to approve at your own
pace — the SDK refreshes the transaction's recent blockhash right before signing,
so a swap that waited on approval doesn't expire.

A word on reading results if you're scripting this: the exit code is honest. A
terminal `denied` or `error` exits non-zero, and so does a request that could not
be made. With `--json`, those failures also come back as a JSON object on stdout,
so you can read one channel whichever way it went. Bad usage is the exception:
a missing flag or an unknown command is reported as plain text on stderr before
the command runs, so check the exit code there rather than parsing stdout.

You can also integrate over the **[API reference](/api-reference)** directly, or
connect an agent via the **[MCP connector](/connect/mcp)**.
