<!--
Sitemap:
- [PayBox](/index): The non-custodial wallet for AI agents
- [Getting started](/getting-started)
- [Credentials & agents](/concepts/model): The PayBox model
- [Approvals](/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)
- [PayBox integration manual](/skill): Connect an AI agent to PayBox: CLI on Muse and Grok Bot, MCP on connector hosts.
-->

# 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
the generic [plugin discovery and invocation flow](/reference/mcp-tools#plugin-tools).
Core operations use the REST API; plugins use the same MCP tools as connector
hosts, with no hardcoded per-plugin paths.
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 **Muse**, **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 device code
paybox login --key pbx_live_…             # personal API key instead (no OAuth)
paybox connect                            # pair a signing key with an eight-emoji code
paybox login --no-open                    # print the link without opening a browser

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 plugins list --status enabled
paybox plugins contract paybox://plugins/world
paybox plugins use world world_get_events --input '{"status":"active"}'

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
```

Plugin commands are discovered at runtime rather than compiled into one command
group per plugin. Run `paybox plugins list`, pass an official result's exact
`contract_uri` to `paybox plugins contract`, then invoke the selected schema with
`paybox plugins use`. Unofficial discovery results include their tools directly.

The `paybox world` and `paybox hyperliquid` command groups were removed in
1.0.0. Reach both through `paybox plugins`, the same way as any other plugin.

### Logging in

Setup is two commands:

1. **`paybox login`** starts an OAuth 2.1 **device-code** grant. The CLI prints a
   verification URL, tries to open it locally, and polls until you choose your
   wallets and approve. 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. **`paybox connect`** pairs a signing key, which `sign` and `swap` need. The
   CLI generates the key on this machine, prints eight emoji and a link, and
   polls. Open the link on any device and pick the row with the same emoji. The
   key stays on this machine and PayBox never holds it; there is nothing to copy
   or paste.

Add `--no-open` to either command to print the link without launching a browser.
`login` clears a stored signing key, because a key paired to the previous client
cannot sign, so run `connect` after it.

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 links are the agent's job.** The agent runs `paybox login --no-open` and
`paybox connect --no-open`, reads the printed links from stderr, and puts each
one in front of you as something you can tap. Without `--no-open` the CLI tries
to open a browser on its own machine, which is useless when the machine is not
yours. If the agent does not relay a link, the command polls until it times out
and nothing tells you why.

**Nothing else should reach the chat.** From the install to a working session you
need exactly two messages: the Connect CLI link, then the eight emoji with the
signing-key link. Nothing is pasted, and the host stores nothing, so the agent
never needs a custom connector, a Client ID, or a host secrets store. Progress
narration in between is noise the agent is adding.

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