# Headless / API

:::warning
This feature is experimental and may change as we iterate.
:::

This is the REST and session-model reference for funding — the substrate under the modal, the WebView, and the SDKs. Call it directly from a backend, a script, or an agent to get a deposit address with no UI. The session model is identical everywhere: create a session for a destination, set a payment method, then read it back until it settles.

:::tip
For SDK-specific integration, use the per-SDK pages instead: the [`useFunding` hook (React)](/docs/products/embedded-wallet/react/wallet/funding), the [wallet modal (React)](/docs/products/embedded-wallet/react/ui/configuration#funding), and [React Native](/docs/products/embedded-wallet/react-native/wallet/funding). This page documents the underlying HTTP API they call.
:::

## Endpoints

| Method | Endpoint | Purpose |
| --- | --- | --- |
| `POST` | `/v2/funding/sessions` | Create a session (optionally with a `paymentMethod` inline). |
| `POST` | `/v2/funding/sessions/{id}/payment_methods` | Commit a source and mint the deposit address. |
| `GET` | `/v2/funding/sessions/{id}` | Read the session and its current status. |
| `GET` | `/v2/funding/chains` | List the routable chains and currencies. |
| `POST` | `/v2/funding/pay_link` | Resolve a prefilled exchange on-ramp URL. |

## Using an SDK

Most integrations don't call these endpoints by hand. For React, see the [`useFunding` hook](/docs/products/embedded-wallet/react/wallet/funding) or the [wallet modal](/docs/products/embedded-wallet/react/ui/configuration#funding); for React Native, see [Funding — React Native](/docs/products/embedded-wallet/react-native/wallet/funding). The rest of this page is the HTTP API those SDKs sit on top of.

## REST API

All three endpoints authenticate with your **publishable key** via `Authorization: Bearer`. The `clientSecret` returned on session creation acts as a secondary guard — pass it on reads and payment-method writes as defense-in-depth.

> **Coming soon:** A secret-key server flow for creating sessions on behalf of any user and listing sessions across your account.

### Create a session

```bash
curl -X POST https://api.openfort.io/v2/funding/sessions \
  -H "Authorization: Bearer $OPENFORT_PUBLISHABLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "target": {
      "chain": "eip155:8453",
      "currency": "0x8335…2913",
      "address": "0xUser…"
    }
  }'
```

Returns a session object containing an `id` and `clientSecret`. Keep the `clientSecret` — you'll need it for subsequent calls.

### Set a payment method

Calling this endpoint mints the deposit address for the session.

```bash
curl -X POST https://api.openfort.io/v2/funding/sessions/{id}/payment_methods \
  -H "Authorization: Bearer $OPENFORT_PUBLISHABLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "clientSecret": "…",
    "paymentMethod": {
      "type": "evm",
      "source": {
        "chain": "eip155:137",
        "currency": "0x3c49…3359",
        "amount": "10000000"
      }
    }
  }'
```

### Read a session

```bash
curl "https://api.openfort.io/v2/funding/sessions/{id}?clientSecret=…" \
  -H "Authorization: Bearer $OPENFORT_PUBLISHABLE_KEY"
```

***

## Payment method types

Pass a `paymentMethod` object either inline on session create or via the `setPaymentMethod` call. Two source types are supported:

:::code-group
### EVM

Fund from any EVM-compatible chain. Specify a CAIP-2 chain ID, ERC-20 contract address, and amount in the token's base unit.

```ts [EVM]
{
  type: 'evm',
  source: {
    chain: 'eip155:137',       // Polygon
    currency: '0x3c49…3359',   // USDC on Polygon
    amount: '10000000'          // 10 USDC (6 decimals)
  }
}
```

### Solana

Fund from Solana mainnet using any SPL token.

```ts [Solana]
{
  type: 'solana',
  source: {
    chain: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',  // Solana mainnet
    currency: 'EPjF…Dt1v',                               // USDC on Solana
    amount: '10000000'                                    // 10 USDC (6 decimals)
  }
}
```
:::

* **`evm` / `solana`** — self-custody transfers. The response carries `receiverAddress`, `addressUri` (for a QR), and wallet `deeplinks`.

Both are accepted by the REST endpoint. Exchange funding is a separate rail — see [`pay_link`](#endpoints) below.

:::tip[Show fees and the minimum before the user sends]
Once a payment method is set, `session.paymentMethod.fees` (an array of `{ kind, amount, currency }` where `kind` is `gas` | `relayerGas` | `relayerService` | `app`) and `session.paymentMethod.minAmount` (in base units, or `null` for no floor) carry the cost and minimum for the route. Surface both so the user sees what it costs and the smallest amount they can send before they transfer.
:::

:::note
**Fiat (card / Apple Pay) isn't available headlessly yet** — it's a React-modal-only method (see [wallet modal](/docs/products/embedded-wallet/react/ui/configuration#funding)). Headless callers fund via the crypto and exchange rails above.
:::

## Response fields

A `FundingSession` (returned by `create`, `setPaymentMethod`, `get`, and `wait`):

| Field | Description |
| --- | --- |
| `id` | Session identifier. |
| `status` | One of `requires_payment_method` | `waiting_payment` | `processing` | `succeeded` | `bounced` | `expired`. |
| `clientSecret` | Secret that guards reads and `setPaymentMethod`; the SDK remembers it per session. |
| `target` | Destination `{ chain, currency, address }`. |
| `amountUnits` | Locked deposit amount in destination base units, or `null` when the sender chooses. |
| `metadata` | Arbitrary string map stored with the session, or `null`. |
| `externalId` | Idempotency / correlation key, or `null`. |
| `strict` | `true` for a single-use deposit address, `false` for an open reusable one. |
| `paymentMethod` | The committed source route (see below); `null` until a payment method is set. |
| `createdAt` | When the session was created. |
| `expiresAt` | When the session expires. |

The `paymentMethod` (a `FundingPaymentMethod`):

| Field | Description |
| --- | --- |
| `type` | `evm` | `solana`. |
| `source` | Source `{ chain, currency, amount }`. |
| `receiverAddress` | Address the user sends to. |
| `addressUri` | EIP-681 / Solana Pay URI for a QR. |
| `deeplinks` | Wallet deeplinks for the transfer (array). |
| `fees` | Array of `{ kind, amount, currency }` route fees. |
| `minAmount` | Minimum sendable amount in base units, or `null`. |

## Session options

`create` accepts a few extras:

| Field | Description |
| --- | --- |
| `paymentMethod` | Set the source route in the same call (one-call funding). The response comes back in `waiting_payment` with the deposit address. |
| `amountUnits` | Lock the deposit to a fixed amount (destination base units). Omit to let the sender choose. |
| `externalId` | Idempotency / correlation key. Reusing it returns the existing session — unchanged, even if `paymentMethod` is supplied (advance it with `setPaymentMethod` + its `clientSecret`). |
| `metadata` | Arbitrary string map stored with the session. |
| `strict` | `true` mints a single-use deposit address; `false` (default) mints an open address reusable for the route. |

## Refunds

If a deposit can't be completed, the session reaches `bounced` and the funds are refunded on the **source** chain (less gas) to `refundTo`. `refundTo` is set on `paymentMethod` and defaults to the target address for same-VM routes; it is **required** for cross-VM routes (e.g. Solana → Base) because the destination address isn't a valid address on the source chain.

:::note
A `bounced` status is terminal for the session — the source funds are returned, not the destination. Set `refundTo` to an address you control on the source chain whenever the source and target are on different VMs.
:::

## Supported chains & currencies

Fetch the full set of routable chains and currencies directly from the rail — don't hardcode them. The React pickers use this same endpoint internally.

```bash
curl https://api.openfort.io/v2/funding/chains \
  -H "Authorization: Bearer $OPENFORT_PUBLISHABLE_KEY"
```

The React modal filters this list via `uiConfig.funding.sourceChains` / `sourceCurrencies` (see [Wallet Modal configuration](#)). In headless mode, pick any chain + currency from the response directly.

**Common chains — copy-paste ready**

| Chain | CAIP-2 `chain` | USDC `currency` |
|---|---|---|
| Base | `eip155:8453` | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
| Polygon | `eip155:137` | `0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359` |
| Arbitrum | `eip155:42161` | `0xaf88d065e77c8cC2239327C5EDb3A432268e5831` |
| Optimism | `eip155:10` | `0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85` |
| Ethereum | `eip155:1` | `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` |
| Solana | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` |

> To fund with a chain's **native asset** (ETH, MATIC, SOL), use `0x0000000000000000000000000000000000000000` as the `currency`.

### Agent-native usage

There's no UI in this path. An agent funds a wallet by calling `create` → `setPaymentMethod` → polling `get` until the session reaches `succeeded`. At that point, if the destination is a backend wallet, your agent can act on the funds immediately.

A few things worth knowing:

* **Variable amounts** — the deposit address accepts any amount above the route minimum. You don't need to send the exact quoted amount.
* **Fresh addresses** — each session mints a new deposit address.
* **Idempotency** — pass the same `externalId` to retrieve an existing session instead of minting a new one.

## Next steps

* Avoid polling — subscribe to [Webhooks](/docs/configuration/webhooks#funding-events) for status changes.
* Render the flow for users with the [React wallet modal](/docs/products/embedded-wallet/react/ui/configuration#funding) or the [hosted deposit page](/docs/configuration/funding#hosted-deposit-page).
