# Panes

Canonical: https://usepanes.com/docs/funding

## Funding

Panes is prepaid. Signup provides no credit. There are no automatic top-ups or usage overages. Each top-up is **$10 to $10,000**, expressed as an integer number of USD cents. Add as many settled top-ups as you need; total account credit has no purchase cap. The full purchase amount becomes credit.

GET `/v1/payment-methods` before choosing a rail. It lists only currently enabled methods. `stripe_mpp` uses a Stripe Shared Payment Token; `usdc_x402` uses USDC on Base; `checkout` opens a browser checkout. A card wallet may require its owner to approve a payment. A funded wallet with delegated spending authority can pay without that step.

### Create one immutable quote

```bash
curl --fail-with-body -sS "$PANES_API/v1/topups" \
  -H "Authorization: Bearer $PANES_API_KEY" -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $TOPUP_REQUEST_KEY" \
  -d '{"amount_cents":1000,"method":"usdc_x402"}' -o topup.json
export TOPUP_ID="$(jq -er .id topup.json)"
export PAY_URL="$(jq -er .pay_url topup.json)"
export STATUS_URL="$(jq -er .status_url topup.json)"
```

Keep the same request key, amount and method on retry. Quotes expire after 30 minutes. A new quote does not charge anything. POST `pay_url` without a credential returns HTTP 402 and the protocol challenge.

### USDC x402: HTTP wallet signing

The quote's `payment.accepts[0]` contains the exact recipient, token, amount and network. Mainnet is Base (`eip155:8453`), using native USDC with 6 decimals. Do not send another token or use another chain.

The requests below use an authorized EVM wallet exposing `eth_signTypedData_v4` over JSON-RPC. Set `WALLET_RPC_URL` and `WALLET_ADDRESS` from that wallet. It must hold at least the quoted amount of USDC. The facilitator submits the authorization and pays transaction gas. Never expose an unlocked signer publicly.

```bash
export NONCE="0x$(openssl rand -hex 32)"
export VALID_BEFORE="$(($(date +%s) + 240))"
jq --arg from "$WALLET_ADDRESS" --arg nonce "$NONCE" --arg end "$VALID_BEFORE" '
  .payment.accepts[0] as $r |
  {domain:{name:$r.extra.name,version:$r.extra.version,
           chainId:($r.network|split(":")[1]|tonumber),verifyingContract:$r.asset},
   types:{EIP712Domain:[{name:"name",type:"string"},{name:"version",type:"string"},
                       {name:"chainId",type:"uint256"},{name:"verifyingContract",type:"address"}],
          TransferWithAuthorization:[{name:"from",type:"address"},{name:"to",type:"address"},
                       {name:"value",type:"uint256"},{name:"validAfter",type:"uint256"},
                       {name:"validBefore",type:"uint256"},{name:"nonce",type:"bytes32"}]},
   primaryType:"TransferWithAuthorization",
   message:{from:$from,to:$r.payTo,value:$r.amount,validAfter:"0",validBefore:$end,nonce:$nonce}}
' topup.json > typed-data.json
jq -n --arg from "$WALLET_ADDRESS" --arg typed "$(jq -c . typed-data.json)" \
  '{jsonrpc:"2.0",id:1,method:"eth_signTypedData_v4",params:[$from,$typed]}' > sign-request.json
curl --fail-with-body -sS "$WALLET_RPC_URL" -H 'Content-Type: application/json' \
  --data-binary @sign-request.json -o signature.json
export SIGNATURE="$(jq -er '.result | select(startswith("0x"))' signature.json)"
jq --arg signature "$SIGNATURE" --slurpfile typed typed-data.json '
  {x402Version:2,resource:.payment.resource,accepted:.payment.accepts[0],
   payload:{signature:$signature,authorization:$typed[0].message}}
' topup.json > payment-payload.json
export PAYMENT_SIGNATURE="$(jq -c . payment-payload.json | tr -d '\n' | base64 | tr -d '\n')"
curl --fail-with-body -sS -X POST "$PAY_URL" \
  -H "Authorization: Bearer $PANES_API_KEY" \
  -H "PAYMENT-SIGNATURE: $PAYMENT_SIGNATURE" -o payment.json
```

For Coinbase CDP Server Wallet, use [EIP-712 signing](https://docs.cdp.coinbase.com/server-wallets/v2/evm-features/eip-712-signing) with the same typed data. An x402-capable wallet can also handle the 402 exchange directly. Keep the original quote and payment payload until the status is settled.

### Stripe MPP: scoped card authorization

Create a quote with `"method":"stripe_mpp"`. Its `payment` is an MPP Stripe `charge` challenge. Authorize its amount, currency, merchant `networkId` and expiry through your wallet's SPT API. The resulting `spt_…` is a payment credential; it is not your Panes API key.

Use an MPP wallet to construct the `Payment-Authorization` header. The server advertises that header so `Authorization` remains available for the Panes key:

```bash
curl --fail-with-body -sS -X POST "$PAY_URL" \
  -H "Authorization: Bearer $PANES_API_KEY" \
  -H "Payment-Authorization: $PAYMENT_AUTHORIZATION" -o payment.json
```

The credential payload is `{"spt":"spt_…","externalId":"<topup.id>"}`; echo the issued challenge unchanged. [MPP wallet documentation](https://mpp.dev/tools/wallet).

### Confirm settlement and recover

```bash
curl --fail-with-body -sS "$STATUS_URL" \
  -H "Authorization: Bearer $PANES_API_KEY" -o payment-status.json
jq '{id,status,balance_usd,next_action}' payment-status.json
```

| Status | What to do |
| --- | --- |
| `quoted` | Authorize this quote before its expiry. |
| `pending` | Poll the same status URL after 3 seconds. Do not pay again. |
| `paid` | Credit is available. Save the receipt and continue. |
| `failed` | This attempt did not settle. Use a new quote and an authorized payment method. |
| `expired` | No payment was submitted. Create a new quote with a new request key. |
| `reconcile` | Do not pay again. Contact support@usepanes.com with the top-up ID. |
| `refunded` | The credit was reversed. Check your balance before starting work. |

A payment can settle even when its HTTP reply is lost. GET status resumes the stored attempt. Panes stops automatic Stripe creation retries before its idempotency guarantee expires. An ambiguous on-chain settlement is held for reconciliation instead of asking for another transfer.

### Browser fallback

Create a `checkout` quote, then POST its `pay_url`. Open the returned `checkout_url` in a browser and complete Stripe Checkout. Poll `status_url` afterward. This rail requires a browser and does not satisfy unattended funding.

Refunds and disputes affect the same prepaid balance regardless of rail. A dispute blocks new compute immediately and gives running computers five minutes to save work. The [refund policy](/refunds) governs refunds.
