x402 Credit Facilitator
Pay for any x402-enabled API with Floe credit. No pre-funding, no wallet management — delegate your collateral and the facilitator handles everything.
You can also set up agents through the Developer Dashboard — a web UI at
dev-dashboard.floelabs.xyz.
Works with 13,000+ existing x402 APIs on Base — no per-service integration needed.
Protocol versions
The facilitator speaks both x402 v1 and x402 v2. Version is negotiated per request by inspecting the 402 response your target API returned — you do not select a version yourself.
PAYMENT-REQUIRED header value
base64 of a single PaymentRequirement, or an array of them
base64 of a { x402Version: 2, accepts: [...], resource?, error?, extensions? } envelope
Amount field name (on the wire)
maxAmountRequired
amount
Network identifier
short name ("base") or CAIP-2 ("eip155:8453")
CAIP-2 only ("eip155:8453")
Outbound payment header
X-PAYMENT
PAYMENT-SIGNATURE
Settlement response header
X-PAYMENT-RESPONSE (free-form value)
PAYMENT-RESPONSE (base64 of SettlementResponse)
EIP-3009 typed data
identical
identical
What this means for you:
If your target API is a
@x402/hono(v2) server, the facilitator parses its envelope, picks the Base + USDC offer fromaccepts, and writes a v2PAYMENT-SIGNATURErequest.If your target API still emits a v1 bare requirement, the facilitator handles that path with
X-PAYMENTexactly as before.The
GET /v1/proxy/checkprobe surfaces the negotiated version as anx402Versionfield in its JSON response so you can sanity-check upstream behavior.
The facilitator only accepts offers with scheme: "exact" on Base mainnet (network: "base" or "eip155:8453") paid in USDC. Other schemes and networks are filtered out before payment is attempted.
Spec references: x402 v2 specification, v2 HTTP transport, CDP migration guide.
How It Works
The managed-agent pattern is the abstraction boundary: you provision a Floe credit agent once, and the facilitator handles everything else on-chain. The developer's local wallet only signs API auth headers — Floe constructs and submits the on-chain operator delegation server-side from the agent's Privy wallet. The agent at runtime never signs intents, never manages loans, never touches EIP-3009. It just calls fetch() and the facilitator does the rest.
The on-chain primitives the facilitator relies on (all submitted server-side from the agent's Privy wallet):
setOperator(operator, OperatorPermission)— Floe submits this at provisioning time to grant the facilitator scoped delegation.revokeOperator(operator)— submitted on agent close (or directly by the Privy wallet on any other revocation path).getOperatorPermission(agent, operator)— read by the facilitator before every borrow match to re-validate the constraints below.
The OperatorPermission struct (enforced by the LendingIntentMatcher contract at every match):
All five constraints are re-validated at the moment of each borrow match, so the facilitator provably cannot exceed them even if compromised.
Reservation Lifecycle (RC-12)
Every paid call through /proxy/fetch creates a reservation that tracks the EIP-3009 authorization from the moment it is signed through final on-chain settlement. Reservations are the facilitator's double-charge defense: a single agent balance can be debited for an in-flight authorization at most once, and reconciliation closes out every reservation either as settled or as fully released.
reserved
yes
Waiting — no action
sent
yes
Waiting — no action
pending_settlement
yes
Do not retry immediately. Reconciliation runs every 15s; poll GET /agents/:id/balance (returns a pendingSettlements field) until the reservation finalizes.
settled
no (consumed)
Done — tx hash available via admin endpoints
expired_unsettled
no (released)
Safe to retry, possibly with a different provider
payment_rejected
no (released)
Safe to retry immediately — no payment was ever claimed
Ambiguous paid-request failure
When a network error occurs after the facilitator has attached the X-PAYMENT header to the upstream request, the reservation transitions to pending_settlement rather than payment_rejected. The merchant may already have called transferWithAuthorization on-chain even though our socket died before the response came back, so the outcome is not yet decidable. In this case /proxy/fetch returns HTTP 502 with:
Agents must not retry immediately. The reconciliation loop will finalize the reservation to settled (if a matching USDC Transfer is observed on-chain) or expired_unsettled (if validBefore passes with no transfer). Typical resolution latency is 15s–90s.
Settlement deadline
The EIP-3009 authorization is signed with a validBefore timestamp set X402_VALID_BEFORE_SECONDS ahead of now (default 90). If the facilitator receives a 2xx upstream response but validBefore has already passed, the merchant can no longer claim the authorization on-chain — so the reservation is released and /proxy/fetch returns HTTP 502 with:
This is safe to retry immediately, ideally against a different provider that may respond faster.
Why 502 and not 202
The facilitator made a paid upstream call and did not receive a confirmable settlement — this is a bad-gateway condition between the agent and a merchant the facilitator could not transact with cleanly, not a pending async response.
Quick Start
With AgentKit CLI (recommended)
The simplest way to register an agent and get an API key:
The CLI signs a wallet auth message, calls POST /v1/developer/agents to provision a managed Privy wallet (with server-side setOperator delegation), mints a floe_* key via POST /v1/developer/agents/:id/keys, and stores the key in your OS keychain. The key is printed once.
With AgentKit action (in-conversation)
If you want an LLM to register an agent during a chat session, the grant_credit_delegation action wraps the same two API calls:
The action prints the key once and stores it in-memory for the rest of the session. For persistence, prefer the CLI above.
With curl
The flow uses two different credentials. Steps 1 and 2 are management calls authenticated by the developer's wallet signature (or, equivalently, a floe_live_* developer key — pick whichever fits your stack). Step 3 is an agent runtime call authenticated by the floe_* key minted in step 2. See the auth table in Credit API → Authentication for the full breakdown.
With Python
Or use the REST API directly. API_KEY here is the agent's floe_* runtime key (not the floe_live_* developer key):
Registration
Registration is a two-step API call, both authenticated with a wallet signature:
Create the agent (
POST /v1/developer/agents) — Floe provisions a managed Privy wallet for the agent, then issues the on-chainsetOperatordelegation from that Privy wallet to the facilitator. You don't send any on-chain transactions from your local wallet. Body:{ name, borrowLimitRaw, maxRateBps, expirySeconds }. Returns{ agentId, status, privyWalletAddress, delegationTxHash }.Mint an API key (
POST /v1/developer/agents/:agentId/keys) — issues afloe_*key scoped to that agent. Optional body{ label, permissions }. The full key is returned once; only its prefix is persisted server-side. Each agent has a one-active-key cap — usePOST /keys/:keyId/rotateto swap atomically.
Both calls accept any of three credentials interchangeably: a dashboard session cookie, a floe_live_* developer key, or a wallet-signature header set (X-Wallet-Address + X-Signature + X-Timestamp). The agentkit SDKs use the signature path so users don't need to obtain a developer key first.
Wallet signature format
Signed with the developer's wallet via personal_sign / EIP-191. The middleware verifies the recovered signer matches X-Wallet-Address and rejects timestamps more than ±5 minutes from server time. EOA (ECDSA), deployed ERC-1271 smart wallets, and undeployed ERC-6492-wrapped smart wallets are all accepted.
Managed Privy wallets
Each agent owns its own server-managed Privy wallet. The Privy wallet is the on-chain identity that holds collateral, takes facility loans, and pays merchants via the facilitator. The developer's wallet is only used to authenticate management calls — it never signs settlement or setOperator transactions.
This replaces the older two-step "pre-register → sign setOperator → register" dance. There is no onBehalfOfRestriction to set: the Privy wallet is the borrowing identity.
AgentKit Actions
grant_credit_delegation
Setup
One-shot: provisions a managed Privy wallet, delegates the facilitator server-side, mints an API key. Takes name, borrowLimit, maxRateBps, expiryDays. Prefer the floe-agent register CLI for persistent multi-agent setups.
revoke_credit_delegation
Teardown
Calls revokeOperator on-chain to revoke a facilitator delegation from your local wallet (legacy on-chain operation; not used for managed agents created via grant_credit_delegation).
check_credit_delegation
Read
Reads getOperatorPermission on-chain from your local wallet for the given operator (legacy on-chain operation).
x402_fetch
Proxy
Fetch any URL — auto-pays if 402, passthrough if free
x402_get_balance
Read
Credit status: limit, used, available, active loans
x402_get_transactions
Read
Payment history with pagination
REST API Reference
Base URL: https://credit-api.floelabs.xyz
Public (No Auth)
GET /health
Liveness probe.
For agent registration endpoints (POST /v1/developer/agents, POST /v1/developer/agents/:id/keys, list/revoke/rotate/close), see Credit API → Developer Agents.
GET /v1/proxy/check
Check if a URL requires x402 payment (unauthenticated probe).
On a 402 response with a parseable PAYMENT-REQUIRED header, the body is:
x402Version is 1 or 2 depending on which envelope shape the merchant returned. If the header can't be parsed, the response is 502 with code set to one of invalid_base64, invalid_json, or no_compatible_requirement so you can tell whether the upstream is misformatting the header or offering a payment scheme/network Floe doesn't support.
Authenticated (Bearer token)
POST /v1/proxy/fetch
Proxy a request. Handles x402 payments automatically.
Request headers
Authorization: Bearer floe_…
yes
Agent API key
Content-Type: application/json
yes
—
Idempotency-Key: <opaque>
no
Stripe-style retry-safe key (≤255 chars). Same key + same agent within 10 min replays the cached response instead of paying again. See Idempotency below.
Response headers (success)
X-Floe-Cost-USDC
on 2xx paid responses
Raw USDC units (6-decimal integer string) actually charged for this call. Set by the facilitator after a successful x402 settlement; absent on free passthrough responses.
X-Floe-Idempotent-Replay: true
on replays only
Indicates the response body is a cached replay of a prior request with the same Idempotency-Key. Absent on the first attempt and on requests without a key.
200
Success — response from target
400
Invalid request or blocked URL
401
Invalid API key
402
Insufficient credit
403
Account frozen or closed
409
Idempotency-Key is currently in-flight on another request — see Idempotency
429
Rate limit exceeded — see body shape below
502
Target unreachable, or paid-request failure (see Reservation Lifecycle)
A 429 response body looks like:
reason distinguishes the three rate-limit sources so an agent can decide whether to wait, slow down, or fall back to a free path:
reason
Source
Agent action
agent_proxy_limit
Per-agent token bucket (default 30/min, RC12_RATE_LIMIT_PER_MINUTE). The standard /proxy/fetch ceiling.
Wait retry_after_seconds; safe to retry.
ip_rate_limit
Per-IP sliding window (covers /proxy/check and /x402/estimate).
Wait, or check if the IP is shared with other callers.
global_rate_limit
Server-wide protection on the /v1/* surface.
Wait longer — may indicate platform overload.
limit_per_minute echoes the per-bucket cap; remaining is the number of tokens left in the current window (always 0 on rejection).
Idempotency
POST /v1/proxy/fetch accepts a Stripe-style Idempotency-Key request header to make retries safe across network failures. Without it, a retry after a transient 502 or socket error can trigger a second upstream payment.
Rules
Send
Idempotency-Key: <opaque-key>(any string up to 255 chars — typically a UUIDv4) along with yourAuthorizationheader.Within a 10-minute window, the same key from the same agent replays the original response byte-for-byte (status + headers + body) plus
X-Floe-Idempotent-Replay: true.If a previous request with that key is still in flight, a concurrent retry receives
409 Conflictwith{ "error": "request_in_flight", "idempotency_key": "<key>" }. Wait and retry, or generate a fresh key.Requests without the header skip idempotency entirely (backward-compatible).
Keys are scoped per-agent — two agents may share a key without colliding.
Replays do not consume rate-limit tokens and do not trigger a second upstream call.
Stripe's contract applies: the response body is cached regardless of status (2xx, 4xx, 5xx), so a retry against the same key returns the same answer — generate a new key for a logically new attempt.
GET /v1/agents/balance
pendingSettlements (RC-12): sum of reservations in pending_settlement state — authorizations that have been signed and sent but not yet confirmed on-chain by the reconciliation loop. This amount is temporarily reserved against the agent's credit limit until the reconciliation loop finalizes each reservation to settled or expired_unsettled. See Reservation Lifecycle (RC-12).
GET /v1/agents/transactions
Paginated payment history.
POST /v1/agents/close
Initiate wind-down. Repays all loans, transfers remaining USDC to your wallet, closes account.
Credit Model
Your credit is backed by on-chain collateral (ETH or cbBTC) via Floe's lending protocol. The facilitator:
Borrows USDC against your collateral when your payment wallet runs low
Monitors collateral health and freezes spending before you're at risk
Rolls over loans before they expire so your credit stays active
Repays everything when you revoke delegation or close your account
You never manage loans directly. The facilitator handles the entire lifecycle.
OperatorPermission parameters
For managed agents (created via POST /v1/developer/agents or the CLI), Floe constructs and submits setOperator server-side from the agent's Privy wallet — you never set these parameters directly. The values you choose at provisioning time map to the on-chain fields as follows:
borrowLimitRaw (POST /v1/developer/agents) / --borrow-limit (CLI)
borrowLimit (uint256)
Raw USDC, 6 decimals. CLI flag is in USDC for convenience.
maxRateBps
maxRateBps (uint256)
Interest rate ceiling in basis points (e.g. 1500 = 15% APR).
expirySeconds / --expiry-days
expiry (uint256)
Server adds expirySeconds to now before submitting.
(server-managed)
operator (address)
The facilitator's operator EOA.
(server-managed)
onBehalfOfRestriction (address)
Set to the agent's own Privy wallet by the server. There is nothing for the caller to pass here.
All five fields are enforced on-chain by the LendingIntentMatcher contract at every borrow match — the facilitator cannot exceed any of them. Because the Privy wallet IS the on-chain borrowing identity, there is no separate onBehalfOfRestriction for the caller to manage.
Revoking delegation
To wind an agent down, use:
REST:
POST /v1/developer/agents/:agentId/close— server triggers a full wind-down: repays all outstanding facility loans, transfers any remaining USDC from the agent's Privy wallet back to the developer, and marks the agentclosed. This is the only path that actually retires the operator permission and frees up the agent slot.On-chain (advanced): the legacy
revokeOperator(operator)action remains available for callers that hold an EOA-issued operator permission outside the managed flow.
floe-agent revoke <name>is not a wind-down. It only revokes the agent's API key (server-side + local keychain entry) — the on-chain operator permission, active loans, and the Privy wallet's USDC balance are untouched. Use it to rotate credentials, not to retire an agent.
The facilitator can no longer register new borrow intents once the operator permission is revoked, and any in-flight intents fail match-time revalidation. Existing active loans remain callable for repay/rollover by the facilitator (intentional — protects against agent-side griefing that would trap an operator mid-loan).
What Happens If Collateral Drops
The facilitator monitors the agent's collateral-to-debt ratio. If it drops too low, new spending is paused until the price recovers or the agent receives more collateral. Active loans are unaffected — they continue to maturity and can be rolled over.
To stop entirely, call POST /v1/developer/agents/:agentId/close. The facilitator repays loans, the agent's collateral returns, and remaining USDC is transferred to the developer.
Last updated
