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.

Aspect
v1
v2

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 from accepts, and writes a v2 PAYMENT-SIGNATURE request.

  • If your target API still emits a v1 bare requirement, the facilitator handles that path with X-PAYMENT exactly as before.

  • The GET /v1/proxy/check probe surfaces the negotiated version as an x402Version field 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 specificationarrow-up-right, v2 HTTP transportarrow-up-right, CDP migration guidearrow-up-right.

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.

State
Balance reserved?
Agent action

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

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:

  1. Create the agent (POST /v1/developer/agents) — Floe provisions a managed Privy wallet for the agent, then issues the on-chain setOperator delegation 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 }.

  2. Mint an API key (POST /v1/developer/agents/:agentId/keys) — issues a floe_* 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 — use POST /keys/:keyId/rotate to 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

Action
Type
Description

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

Header
Required
Purpose

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)

Header
Always set
Purpose

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.

Status
Meaning

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

  1. Send Idempotency-Key: <opaque-key> (any string up to 255 chars — typically a UUIDv4) along with your Authorization header.

  2. 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.

  3. If a previous request with that key is still in flight, a concurrent retry receives 409 Conflict with { "error": "request_in_flight", "idempotency_key": "<key>" }. Wait and retry, or generate a fresh key.

  4. Requests without the header skip idempotency entirely (backward-compatible).

  5. Keys are scoped per-agent — two agents may share a key without colliding.

  6. 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:

Provisioning input
On-chain field
Notes

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 agent closed. 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