> ## Documentation Index
> Fetch the complete documentation index at: https://docs.venlyfinance.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Card Program Setup

> Prepare a cardholder account so card authorizations can reserve and settle funds — the Venly-managed and self-custody flows side by side.

When a cardholder pays, the network sends Venly an **authorization** — Venly reserves the amount from the cardholder's wallet, then **settles** it (money leaves to the company's settlement wallet) or **reverses** it (money returns to the cardholder) as the transaction clears. On the Finance API, that authorization **is a [payment request](/guides/finance/payment-requests)**.

This guide takes a cardholder account from creation to a working authorization, and calls out exactly where **Venly-managed** (`VENLY_MANAGED`) and **self-custody** (`SELF_CUSTODY`) companies differ. The difference is one extra step — self-custody wallets need a one-time permit before they can be charged.

<Info>
  **Base URL (staging):** `https://api-staging.venlyfinance.com/v1`
  **Auth:** OAuth2 Bearer token in the `Authorization` header. Tokens expire after 5 minutes — see the [Authentication guide](/getting-started/authentication).
</Info>

<Note>
  Dedicated card-management endpoints (issue, freeze, list transactions) are on the [roadmap](/guides/card-issuance/introduction). The authorization → settlement plumbing this guide uses is **live today** on the Finance API.
</Note>

***

## Prerequisites

<Steps>
  <Step title="A client provisioned for the card program">
    Your `client_id` must carry the **`finance-api-card`** role plus **`manage:payment-requests`** (and **`manage:all-payment-requests`** if you authorize by card-provider reference). Your Venly contact enables these.
  </Step>

  <Step title="An HTTP client and a UUID generator">
    Examples use `curl`. Money-moving calls need an `idempotencyKey` (UUID v4) — `uuidgen`, `[guid]::NewGuid()`, or `crypto.randomUUID()`.
  </Step>

  <Step title="Self-custody only: the cardholder's wallet">
    A wallet `address` you'll register on the account, plus access to its **owner key** to sign one permit.
  </Step>
</Steps>

<Warning>
  The **`finance-api-card`** role is what provisions the **escrow wallet** every card authorization needs. An account created by a client *without* that role is **non-custodial-only** and rejects authorizations with `escrow-wallet-missing`. The role is applied at **account-create time** — grant it *before* creating the cardholder account; it is not back-filled onto existing accounts.
</Warning>

***

## The flow

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant App as Your App
    participant API as Finance API
    participant Ops as Venly Admin

    App->>API: 1. POST /accounts (cardholder)
    API-->>App: accountId (kycStatus VERIFICATION_PENDING)
    Ops-->>API: 2. Verify account
    App->>API: 3. Fund the account wallet
    Note over App,API: 4. SELF_CUSTODY only — sign + submit permits<br/>(wallet PENDING → ACTIVE)
    App->>API: 5. POST payment-request (authorization)
    API-->>App: status RESERVED
    App->>API: 6. Settle or reverse
```

Venly-managed accounts skip step 4 entirely — their wallets are permitted automatically.

***

## Step 1 — Authenticate

```bash theme={null}
curl -X POST https://login-staging.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"
```

```json Response theme={null}
{ "access_token": "eyJhbGciOi...", "expires_in": 300, "token_type": "Bearer" }
```

<Tip>Cache the token. Refresh on `401` or shortly before `expires_in` — don't re-authenticate on every call.</Tip>

***

## Step 2 — Create the cardholder account

The account represents the cardholder; its wallet funds the card. Optionally attach a **`cardProviderReference`** so authorizations arriving by card-provider reference resolve to this account.

The only structural difference between company types is the wallet `address`:

* **Venly-managed** — Venly creates and holds the wallet. **Omit `address`.**
* **Self-custody** — the cardholder controls the wallet. **Send their `address`** (required).

<CodeGroup>
  ```bash Venly-managed theme={null}
  curl -X POST https://api-staging.venlyfinance.com/v1/accounts \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "externalId": "cardholder-12345",
      "name": "Jane Doe — Card",
      "chain": "BASE",
      "cardProviderReference": { "type": "PAYMENTOLOGY", "referenceId": "ACC-12345" }
    }'
  ```

  ```bash Self-custody theme={null}
  curl -X POST https://api-staging.venlyfinance.com/v1/accounts \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "externalId": "cardholder-12345",
      "name": "Jane Doe — Card",
      "chain": "BASE",
      "address": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
      "cardProviderReference": { "type": "PAYMENTOLOGY", "referenceId": "ACC-12345" }
    }'
  ```
</CodeGroup>

```json Response (201) theme={null}
{
  "success": true,
  "result": {
    "id": "b2a1f0e9-8c7d-4e3a-9f21-0a1b2c3d4e5f",
    "externalId": "cardholder-12345",
    "name": "Jane Doe — Card",
    "kycStatus": "VERIFICATION_PENDING",
    "status": "ACTIVE",
    "createdAt": "2026-01-15T09:30:00",
    "version": 0
  }
}
```

<Tip>`cardProviderReference` can only be set when the account is **created**. Decide the card link up front.</Tip>

***

## Step 3 — Verify the account

A new account is `kycStatus: VERIFICATION_PENDING`. A **Venly admin** reviews and verifies it — there's no API call to trigger this. Poll until it flips:

```bash theme={null}
curl https://api-staging.venlyfinance.com/v1/accounts/{accountId} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

```json Response (200) theme={null}
{ "success": true, "result": { "id": "b2a1f0e9-...", "kycStatus": "VERIFIED", "status": "ACTIVE", "version": 1 } }
```

Until `VERIFIED`, authorizations fail with `account-not-active` ("KYC status must be VERIFIED"). See [Account verification](/guides/finance/kyc-verification).

***

## Step 4 — Fund the wallet

The account wallet must hold the stablecoin the card spends. Read its address and balance:

```bash theme={null}
curl https://api-staging.venlyfinance.com/v1/accounts/{accountId}/wallets \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

```json Response (200) theme={null}
{
  "success": true,
  "result": [
    {
      "id": "9f8e7d6c-5b4a-4938-8271-6a5b4c3d2e1f",
      "chain": "BASE",
      "type": "VENLY_MANAGED",
      "address": "0x01DA4aa698B545D6A32ef44DB37139D3b27eAF61",
      "balances": [
        { "asset": "USDC", "contractAddress": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "amount": { "total": "0", "available": "0", "reserved": "0" } }
      ],
      "amlStatus": "APPROVED"
    }
  ]
}
```

* **Venly-managed** — fund the returned Venly address with the stablecoin. Gas is covered by Venly's orchestration wallet; the account wallet only needs the token.
* **Self-custody** — the cardholder funds their own `address` with the stablecoin.

***

## Step 5 — Activate the wallet (self-custody only)

<Note>Venly-managed wallets are permitted automatically — **skip to Step 6.**</Note>

A self-custody wallet is created `PENDING`. Before it can be charged, the owner signs a one-time **permit** for **every** supported asset on the wallet, and the wallet becomes **`ACTIVE` only once all of them are `CONFIRMED`**. Until then authorizations fail with `account-wallet-not-active`.

<Warning>
  A raw on-chain `approve()` sets the ERC-20 allowance but does **not** activate the wallet — only a **confirmed permit** does. And it's **all** assets: a wallet that supports USDC *and* EURC needs **both** permits confirmed before it goes `ACTIVE`.
</Warning>

<Steps>
  <Step title="Get the messages to sign">
    `GET /accounts/{accountId}/wallets/{walletId}/permits` returns, per asset, a `supportedAssetId` and an EIP-712 `typedData` object.
  </Step>

  <Step title="Sign each with the owner's key">
    Sign `typedData` with the wallet **owner** key (off-chain, no gas). The signature must recover to the `owner` or the permit becomes `FAILED`. Produces `v`, `r`, `s`.
  </Step>

  <Step title="Submit each signature">
    `POST .../permits` with the `supportedAssetId` and signature → HTTP 200 with `result.status`.
  </Step>

  <Step title="Wait for every asset to confirm">
    Poll `GET .../permits` until each asset is `CONFIRMED`. When the last one confirms, the wallet activates.
  </Step>
</Steps>

```bash theme={null}
curl -X POST https://api-staging.venlyfinance.com/v1/accounts/{accountId}/wallets/{walletId}/permits \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "supportedAssetId": "5d4c5b99-2ce8-40a1-bc1d-173dd400fa89",
    "signature": { "v": "28", "r": "0x...", "s": "0x..." }
  }'
```

Full signing example (ethers.js) and the allowance check are in [Approving transfers without gas](/guides/finance/permits-and-allowances).

***

## Step 6 — Create the authorization

With the account `VERIFIED`, funded, and (self-custody) `ACTIVE`, reserve funds. There are two endpoints:

<CodeGroup>
  ```bash By account theme={null}
  curl -X POST https://api-staging.venlyfinance.com/v1/accounts/{accountId}/payment-requests \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "amount": 25.0,
      "currency": "USD",
      "externalId": "auth-67890",
      "description": "Card authorization #67890",
      "idempotencyKey": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    }'
  ```

  ```bash By card-provider reference theme={null}
  curl -X POST https://api-staging.venlyfinance.com/v1/payment-requests \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "cardProviderReference": { "type": "PAYMENTOLOGY", "referenceId": "ACC-12345" },
      "paymentRequest": {
        "amount": 25.0,
        "currency": "USD",
        "externalId": "auth-67890",
        "description": "Card authorization #67890",
        "idempotencyKey": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
      }
    }'
  ```
</CodeGroup>

```json Response theme={null}
{
  "success": true,
  "result": {
    "id": "d4e5f6a7-b8c9-4012-8345-6789abcdef01",
    "accountId": "b2a1f0e9-8c7d-4e3a-9f21-0a1b2c3d4e5f",
    "amount": { "fiat": 25.0, "crypto": "25.000000" },
    "originalAmount": { "fiat": 25.0, "crypto": "25.000000" },
    "currency": "USD",
    "status": "RESERVED",
    "executions": [
      { "type": "AUTHORIZATION", "chain": "BASE", "asset": "USDC", "amount": 25.0, "status": "RESERVED", "transactionHash": "0xa1b2..." }
    ]
  }
}
```

The reserved amount now shows in the wallet's `reserved` balance. Amounts carry both `fiat` and `crypto` — see [Payment requests](/guides/finance/payment-requests).

<Note>
  The reservation runs on-chain. On fast chains the response is `RESERVED` straight away; on others it returns `PENDING` and flips to `RESERVED` once the authorization transaction confirms. There's **no GET endpoint** — re-send the create with the **same `idempotencyKey` and body** to read the latest state, and match on your `externalId`.
</Note>

***

## Step 7 — Settle or reverse

When the card transaction clears, resolve the hold:

* **[Settle](/api-reference/Finance-API/payment-requests/settle-a-payment-request)** — money moves from escrow to the company's settlement wallet (`SETTLING` → `SETTLED`). The settle amount can be equal to, below, or above the authorized amount.
* **[Reverse](/api-reference/Finance-API/payment-requests/reverse-a-payment-request)** — the full reserved amount returns to the cardholder (`REVERSING` → `REVERSED`). Use it to release a hold you won't charge.

Both have **by-reference** variants that take the same `cardProviderReference` instead of the `accountId`.

***

## Managed vs self-custody at a glance

| Step                         | Venly-managed (`VENLY_MANAGED`) | Self-custody (`SELF_CUSTODY`)               |
| ---------------------------- | ------------------------------- | ------------------------------------------- |
| Create account               | No `address`                    | `address` **required**                      |
| Wallet custody               | Venly holds the key             | Cardholder holds the key                    |
| Funding                      | Fund the Venly-created address  | Cardholder funds their own address          |
| Gas                          | Paid by Venly orchestration     | Paid by Venly orchestration                 |
| **Wallet activation**        | Automatic                       | **Sign a permit for every supported asset** |
| Verify KYC                   | Required                        | Required                                    |
| Authorize / settle / reverse | Identical                       | Identical                                   |

***

## Common pitfalls

<AccordionGroup>
  <Accordion title="escrow-wallet-missing on the authorization">
    The account was created by a client **without `finance-api-card`**, so it's non-custodial-only and has no escrow wallet. Grant the role, then create a **new** cardholder account (escrow isn't back-filled onto existing accounts).
  </Accordion>

  <Accordion title="account-wallet-not-active (status PENDING)">
    A self-custody wallet that isn't `ACTIVE` yet. Confirm a permit for **every** supported asset on the wallet — the wallet only activates once they're all `CONFIRMED`. A raw `approve()` doesn't count. See [Permits & allowances](/guides/finance/permits-and-allowances).
  </Accordion>

  <Accordion title="account-not-active (KYC status must be VERIFIED)">
    The account is still `VERIFICATION_PENDING`. A Venly admin must verify it — poll `GET /accounts/{id}` until `kycStatus` is `VERIFIED`.
  </Accordion>

  <Accordion title="Address is required for SELF_CUSTODY wallet type">
    Self-custody accounts must include the cardholder's wallet `address` on creation. Venly-managed accounts must not.
  </Accordion>

  <Accordion title="Token expired mid-flow">
    Tokens last 5 minutes. Catch `401`, refresh, and retry — don't pre-authenticate every request.
  </Accordion>
</AccordionGroup>

***

## Next steps

<CardGroup cols={2}>
  <Card title="Payment requests" icon="money-check-dollar" href="/guides/finance/payment-requests">
    The full reserve → settle/reverse/adjust lifecycle.
  </Card>

  <Card title="Permits & allowances" icon="file-signature" href="/guides/finance/permits-and-allowances">
    Gasless wallet activation for self-custody accounts.
  </Card>

  <Card title="Create a payment request" icon="code" href="/api-reference/Finance-API/payment-requests/create-payment-request-for-account">
    Endpoint reference, fields, and error codes.
  </Card>

  <Card title="Card Issuance overview" icon="credit-card" href="/guides/card-issuance/introduction">
    The white-label card program and its roadmap.
  </Card>
</CardGroup>
