Paying with USDT
Sable runs on prepaid credit. You fund a balance with USDT on a supported EVM
chain: Ethereum mainnet (chain_id: 1) and Arbitrum One (42161), and
inference and sandbox compute draw it down. There is no card, no invoice, and
no signup call.
Where a deployment has it switched on, you can also top up gas-free by signing (USDC on Base, USDG on Robinhood Chain) or let an agent pay per request with no account — see Gas-free payments and pay per request.
How settlement works
- Send USDT to the treasury address from a wallet linked to your account.
- Submit the transaction hash.
- The gateway verifies the transfer against its own RPC node and credits your balance once it has enough confirmations.
GET /v1/billing/methods is public and returns the treasury address, the
supported chains, each chain's USDT contract and decimals, and the confirmation
requirement. That response (or the portal's Billing page) is the
authoritative source for the treasury address: addresses can rotate, so never
hardcode one.
# 1. Discover where and how to pay (no credentials needed)
curl https://api.buildsable.com/v1/billing/methods
# 2. After sending USDT, submit the transaction hash
curl https://api.buildsable.com/v1/billing/deposits \
-H "authorization: Bearer $SABLE_SESSION" \
-H 'content-type: application/json' \
-d '{"chain_id": 1, "tx_hash": "0xabc…"}'
# 3. Check the balance
curl https://api.buildsable.com/v1/billing/balance \
-H "authorization: Bearer $SABLE_SESSION"Why the sending wallet matters
A deposit is credited only if it came from a wallet already linked to your
account. This applies to every settlement path, including the inline
X-PAYMENT header below. Without that rule, anyone watching the treasury could
claim someone else's deposit by submitting its hash first. A transfer from an
unlinked wallet is refused with "that transfer was not sent from a wallet
linked to this account"; the remedy is to link the paying wallet first under
Portal → Wallets (linking requires a browser SIWE signature, which is what
proves ownership) and then re-submit the same hash: the funds are not lost,
just uncredited until the wallet is yours on record.
One transaction can carry several treasury-bound transfers (a batched or contract-mediated payment, say), and each qualifying transfer in it is credited individually.
Replay safety
Each on-chain transfer can be credited exactly once, globally: enforced by a
unique constraint on (chain_id, tx_hash, log_index), not by application logic.
Submitting the same hash repeatedly is safe and is in fact how you poll a deposit
that hasn't confirmed yet.
Confirmations
Below the chain's confirmation threshold a deposit comes back pending with its
current count. Re-submit (or use POST /v1/billing/deposits/refresh) and it
credits once confirmed. Waiting for confirmations is what stops a reorg from
handing out credit that was never really paid.
The ledger
Your balance is the signed sum of an append-only ledger: deposits, grants, and
one debit per metered request. Nothing is ever overwritten, so every movement is
auditable, and GET /v1/billing/ledger returns it a page at a time: ?limit=
(1-1000, default 200), then pass the response's next_cursor back as ?cursor=
until has_more is false. Reading only the first page gives you the most recent
movements, not the whole trail.
Debits are idempotent per usage event: a retried metering write can't bill you twice, because the database rejects the duplicate rather than trusting the code to remember.
Agents: paying inline with x402
An out-of-credit request returns 402 with an accepts array describing how to
pay, so an autonomous agent can top itself up and retry without a human:
{
"error": { "message": "account is out of credit…", "type": "insufficient_credit" },
"x402Version": 1,
"accepts": [{
"scheme": "sable-usdt-onchain",
"network": "eip155:1",
"asset": "0xdac1…",
"payTo": "0x…",
"maxAmountRequired": "5000000",
"extra": { "minConfirmations": 12, "settleHeader": "x-payment" }
}]
}
maxAmountRequired is a suggested minimum top-up, $5, expressed in the
asset's base units (5000000 for 6-decimal USDT), not a per-request price.
To settle, replay the request with an X-PAYMENT header carrying
{"chain_id": 1, "tx_hash": "0x…"} (raw JSON or base64). The transfer is
verified and credited before the handler runs, so the retry succeeds in one round
trip. The linked-wallet rule above applies here too: an X-PAYMENT transfer
sent from a wallet that isn't already linked to the account is refused with
"that transfer was not sent from a wallet linked to this account", and linking
requires a browser SIWE signature, so link the agent's paying wallet before
it needs to settle autonomously.
curl https://api.buildsable.com/v1/chat/completions \
-H "authorization: Bearer $SABLE_API_KEY" \
-H "x-payment: $(echo -n '{"chain_id":1,"tx_hash":"0xabc…"}' | base64)" \
-H 'content-type: application/json' \
-d '{"model":"sable-llama-3.3-70b","messages":[{"role":"user","content":"hi"}]}'On the scheme name
The scheme is sable-usdt-onchain, not x402's canonical exact. Canonical
exact settlement is a gasless pull: the client signs an EIP-3009
transferWithAuthorization and a facilitator pulls the funds. USDT predates
EIP-3009 and implements neither it nor EIP-2612 permit on the chains that
matter, so that flow simply isn't available for USDT. Rather than advertise
compatibility we don't have, the settlement step is named for what it actually is.
The 402 envelope and discovery shape are unchanged.
For most agents the cheaper pattern is to deposit once and draw down the prepaid balance: one on-chain transaction total, instead of one per call.
Pricing
Per-model prices are published in GET /v1/models and the exact cost of every
call rides along in its receipt. Sandbox compute bills in
vCPU-seconds and GB-seconds of memory. See Sandbox compute.
Peg note: USDT is credited at face value, treating 1 USDT as $1. That's a deliberate simplification: it keeps a price oracle out of the payment path.