Skip to content

Quickstart

Two integration paths. Pick the one that matches what you’re already doing:

  • You speak x402 / MPP (or use pay.sh). Use pact pay <tool> <url> — drop-in for pay, works on any 402-gated endpoint. → Calling with pay.sh.
  • You just want to call a curated provider over plain HTTP. Use Market host-swap (no CLI) or pact <url> (with CLI). → Market host swap / CLI gateway.

Both paths assume an allowlisted Solana wallet — see Private beta.

Calling with pay.sh — the 60-second flow

Section titled “Calling with pay.sh — the 60-second flow”

pact pay curl is a drop-in for pay curl: same args, x402 + MPP wire format, exit semantics — plus on-chain refund on 5xx, timeout, or truncated/malformed body.

Works on any 402-gated endpoint. Every call gets a classifier verdict + coverage record; on-chain refund settlement currently runs through the pay-default pool, a subsidized launch float covering pay.sh-wrapped calls during private beta. For curated onboarded providers, the gateway flow (pact <url>, below) is the fully insured alternative.

pact pay and pact approve sign with pay.sh’s wallet directly. No separate pact-managed keypair, no pact init required for this flow — fund pay’s account once and both legs draw from it.

  1. Set the closed-beta gate.

    Terminal window
    export PACT_MAINNET_ENABLED=1
  2. Install and set up pay.sh. pact pay reads pay’s active account from ~/.config/pay/accounts.yml and shells out to pay account export <name> - for the keypair. Install pay, create or import an account, confirm pay account list shows an active entry.

  3. Install the pact CLI. See Install — one npm install -g @q3labs/pact-cli, Node 18+.

  4. Fund pay’s account with mainnet USDC. The address pay shows for the active account signs both the merchant payment and Pact’s settlement authorization. Top it up externally — neither pay nor pact moves USDC. Confirm with:

    Terminal window
    pact balance --json
    # { "status": "ok",
    # "body": { "wallet": "...", "ata": "...", "ata_balance_usdc": 0,
    # "allowance_usdc": 0, "eligible": false, "reason": "no_allowance" } }
  5. Grant a settlement allowance. SPL Token Approve (Solana’s standard way to delegate spending authority on a token account) authorizing the protocol’s SettlementAuthority PDA (a program-derived account, not a wallet) to debit up to <usdc> from your USDC ATA (associated token account) at settlement. Funds stay in your ATA — the protocol doesn’t custody.

    Terminal window
    pact approve 5
  6. Swap pay for pact pay. Same args, exit code, stdout.

    Terminal window
    # Before — pay.sh handles the payment, you eat any 5xx.
    pay curl https://flaky.example/api/v1/quote/AAPL
    # After — same args, same exit code, plus on-chain refund on 5xx.
    pact pay curl https://flaky.example/api/v1/quote/AAPL

    Auto-pay is silent by default (matches pay). Pass --json for the structured envelope:

    Terminal window
    pact pay --json curl https://flaky.example/api/v1/quote/AAPL
    # { "status": "x402_payment_made",
    # "body": { "tool_exit_code": 0, "payment": {...}, "response_body": "..." } }

    On every payment attempt pact pay prints [pact] wallet: pay/<name> (xxxx…yyyy) to stderr so the signing account is visible. macOS may trigger one Touch ID prompt per session to unlock pay’s keypair (cached ~5 min). For CI, set PACT_PRIVATE_KEY.

  7. Check the refund. A failed call settles into an on-chain refund record at the next batch window. Refund credits land back on the same USDC ATA that pay.sh’s active account holds (the wallet that originally paid).

    Terminal window
    pact agents show --json | jq '.body.recent_refunds'

Skip pay’s 402-discovery and call any hostname in Pact’s discovery cache directly. The gateway looks up the slug, takes a premium, and proxies to upstream — no x402 challenge, no retry round-trip.

This path uses a separate per-project keypair at ~/.config/pact/<project>/wallet.json (not pay’s account), provisioned by pact init:

Terminal window
pact init
pact approve 5
pact --json https://api.helius.xyz/v0/addresses/<addr>/balances
pact agents show --json

no_provider (exit 20) means the hostname isn’t onboarded; in private beta that’s terminal — fall back to pact pay <tool> <url> or --raw.

The CLI-free path. Swap the upstream host for market.pactnetwork.io/<endpoint>/; same path, same body. Refund coverage is automatic. The <endpoint> identifier and PACT_MARKET_KEY are issued at onboarding (see Private beta).

Terminal window
curl -X POST https://upstream.example/v1/<path> \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $UPSTREAM_KEY"

Success → you pay. Failure (5xx, timeout, or truncated/malformed body — see the classifier) → principal + premium refunded at the next settlement window.

Full Market spec (headers, error shapes, idempotency): Calling Market.

The CLI ships with a skill — a Markdown file that tells Claude Code, Codex, and other skill-aware runners when to reach for pact instead of curl / fetch / Bash.

Two things, both idempotent:

  1. Installs the skill at .claude/skills/pact/SKILL.md (if absent). The skill’s frontmatter description lists curated provider hostnames so the agent only loads it when calling those.
  2. Appends a snippet to CLAUDE.md / AGENTS.md under ## Paid API calls. Skipped if the marker is already there.

pact init does not create the gateway wallet itself. The per-project state directory (wallet.json, policy.yaml, endpoints-cache.json) is provisioned on the first pact <url> call — see Install.

pact pay doesn’t need pact init — it uses pay.sh’s active account directly. Run pact init only when you want the skill / CLAUDE.md snippet, or you’re about to call curated providers via pact <url>.

For an aggressive substitute-every-pay-call policy, see the standalone pact-pay skill.

The skill instructs the agent to pass --json and parse .status first, then map to action:

StatusWhat the agent should do
okcall succeeded — use body
client_errorYOUR request was wrong (4xx) — don’t retry, surface to user
server_errorupstream failed — refund auto-issued, retry once if idempotent
needs_fundingrun pact approve <usdc> if cap allows; else surface
auto_deposit_cappedhit policy cap — surface session_used_usdc / session_max_usdc
endpoint_pausedprovider disabled — pick another or wait
no_providerhostname unsupported — fall back to pact pay <tool> <url>
discovery_unreachablegateway unreachable — surface and stop
signature_rejectedclock skew — tell user to sync NTP

Status values map 1:1 to exit codes (see Commands → Status taxonomy) so an agent can branch on either.

The agent may run pact approve <amount> on its own if <amount>per_deposit_max_usdc and the session total stays under session_total_max_usdc. Caps live in ~/.config/pact/<project>/policy.yaml:

per_deposit_max_usdc: 5
session_total_max_usdc: 20

On auto_deposit_capped the agent surfaces to the user, never retries with a different cap.

RunnerSkill picked up fromNotes
Claude Code.claude/skills/pact/SKILL.mdLoaded automatically; description gate ensures activation only on listed hostnames.
Codex CLI.claude/skills/pact/SKILL.md + AGENTS.md snippetpact init writes the snippet to AGENTS.md when present.
Custom agentdirect readThe body shape and status taxonomy are stable per-version; pin to pact --version and treat unknown statuses as cli_internal_error.
StatusMeaningWhat to do
okgateway call (pact <url>) succeededuse body
x402_payment_made / mpp_payment_madepact pay succeeded via 402 retryuse body.response_body; body.tool_exit_code is the wrapped tool’s exit
client_erroryour request was wrong (4xx)fix the request; don’t retry
server_errorupstream failedrefund queued; retry once if idempotent
payment_failedpact pay parsed a 402 but the retry was rejectedinspect body.scheme + body.reason
needs_fundingATA / allowance insufficientfund the wallet, run pact approve <usdc>
auto_deposit_cappedhit the policy capraise the cap or run approve manually
no_providerhostname not in discovery cacheuse pact pay <tool> <url> or --raw
endpoint_pausedprovider disabledpick another or wait
unsupported_toolpact pay <tool> where <tool> isn’t curluse pact pay curl …
tool_missing<tool> is supported but not on $PATHinstall it

Full taxonomy and exit codes: Commands → Status taxonomy.

  • Refunds aren’t instant. Settlement is batched — expect refunds within one window. Cadence: Status.
  • client_error is on you. Bad keys, malformed payloads, expired tokens don’t get refunded.
  • Retries don’t double-refund. Each retry is a separate call, independently classified.
  • Allowlist enforced at Market. Without a key, you get 401; the upstream never sees the call.