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

# Card Issuer Program

> End-to-end integration guide: apply, onboard customers, complete KYC, and issue Visa virtual cards under your brand

The Card Issuer Program lets your platform issue Visa virtual cards to your end customers. Yativo handles the card network, compliance, and settlement — your backend drives the entire flow via API.

<Note>
  **Two API namespaces — keep them separate.**

  | Namespace     | Base path                     | Purpose                                                                      |
  | ------------- | ----------------------------- | ---------------------------------------------------------------------------- |
  | Card Issuer   | `/v1/card-issuer/*`           | Your program: apply, check status, manage customers, fund cards              |
  | Card Customer | `/v1/yativo-card/customers/*` | Individual customer lifecycle: OTP, KYC, card creation, freeze, transactions |

  The `/v1/customers/*` path is **WaaS** (wallet sub-accounts for crypto deposits/withdrawals). It has nothing to do with card customers. Do not mix them up.
</Note>

***

## Identity Model

```
Yativo
  └── You (the Issuer) — authenticated via your API Bearer token
        └── Your Customers — identified by yativo_card_id, external_customer_id, or email
```

* **You** are a Yativo user who has been approved into the Card Issuer Program.
* **Your Customers** are the end users of your platform who get a card. They are never authenticated directly against Yativo — all API calls come from your backend using your token.
* `yativo_card_id` is the primary identifier for a card customer. Store it after onboarding.
* `external_customer_id` is your own reference ID — pass it at onboarding and use it to look up the customer later without storing Yativo IDs.

***

## Flow Overview

```
Apply → Approved → (Fund Master Wallet)
                        ↓
       Onboard Customer → OTP → KYC → Source of Funds → Phone
                        ↓
                   Card Issued → Fund Card → Active
```

***

## Step 1 — Check Eligibility

Confirm your account has been granted access before applying.

```bash theme={null}
GET /v1/card-issuer/eligibility
Authorization: Bearer YOUR_TOKEN
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "eligible": true,
    "enrolled": false,
    "status": null
  }
}
```

If `eligible` is `false`, contact your account manager. This gate is admin-only — there is no API call to change it yourself.

***

## Step 2 — Apply

```bash theme={null}
POST /v1/card-issuer/apply
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "funding_structure": "master_wallet",
  "preferred_chain": "SOL",
  "iban_enabled": false
}
```

| Field               | Type    | Required | Description                                                                   |
| ------------------- | ------- | -------- | ----------------------------------------------------------------------------- |
| `funding_structure` | string  | Yes      | `master_wallet` or `non_master` (see below)                                   |
| `preferred_chain`   | string  | No       | `SOL` (default) or `XDC` — the chain your master wallet will receive funds on |
| `iban_enabled`      | boolean | No       | Enable IBAN for cardholders. Defaults to `false`.                             |

### Funding Structures

| Value           | How it works                                                                                                                                |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `master_wallet` | You maintain a shared balance and push funds to each customer card via the Fund Customer API. Best for platforms that control card top-ups. |
| `non_master`    | Each customer receives their own deposit address and funds their card directly. Best for self-serve wallets.                                |

```json Response theme={null}
{
  "success": true,
  "message": "Application submitted. An admin will review your request.",
  "data": {
    "id": "69ecc05a4fc8c9c148ed1a9a",
    "status": "pending",
    "funding_structure": "master_wallet",
    "preferred_chain": "SOL"
  }
}
```

***

## Step 3 — Check Program Status

Applications are reviewed within 1–2 business days. Poll until `status` is `approved`.

```bash theme={null}
GET /v1/card-issuer/status
Authorization: Bearer YOUR_TOKEN
```

```json Approved theme={null}
{
  "success": true,
  "data": {
    "enrolled": true,
    "status": "approved",
    "funding_structure": "master_wallet",
    "preferred_chain": "SOL",
    "enabled_chains": ["SOL"],
    "iban_enabled": false,
    "card_reissue_enabled": true,
    "master_wallets": {
      "sol": {
        "address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
        "balance": 0,
        "last_balance_check": null
      },
      "usdc": {
        "balance": 0,
        "last_balance_check": null
      },
      "eur": {
        "balance": 0,
        "last_balance_check": null
      },
      "gbp": {
        "balance": 0,
        "last_balance_check": null
      }
    },
    "stats": {
      "total_cards_issued": 0,
      "total_funded_amount": 0,
      "total_fees_paid": 0
    },
    "limits": {
      "max_cards": 1000,
      "daily_funding_limit": 50000,
      "monthly_funding_limit": 500000
    }
  }
}
```

<Note>
  On approval, Yativo provisions your master wallet set:

  * **SOL deposit address** (`sol.address`) — send USDC on Solana here to top up your USD balance.
  * **USD balance** — funded automatically when your SOL deposit confirms.
  * **EUR balance** — funded by swapping from USD.
  * **GBP balance** — funded by swapping from USD.

  Balances update in real time. You never interact with the underlying settlement infrastructure directly.
</Note>

***

## Step 4 — Fund Your Master Wallet *(master\_wallet programs only)*

Get your current balances and deposit addresses:

```bash theme={null}
GET /v1/card-issuer/master-wallets
Authorization: Bearer YOUR_TOKEN
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "wallets": {
      "sol": {
        "address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
        "balance": 0,
        "last_balance_check": null
      },
      "usdc": { "balance": 0 },
      "eur":  { "balance": 0 },
      "gbp":  { "balance": 0 }
    }
  }
}
```

Send USDC on Solana to `sol.address`. Once the deposit confirms, your `usdc` balance increases automatically. Swap to `eur` or `gbp` before funding customers who hold those card currencies.

### Swap Currencies

If your customers are in the EU or UK, swap USDC into the correct card currency before funding:

```bash theme={null}
POST /v1/card-issuer/master-wallets/swap
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "from_token": "USD",
  "to_token": "EUR",
  "amount": 1000
}
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "swap_id": "swap_01HX...",
    "status": "initiated",
    "from": { "token": "USD", "amount": 1000 },
    "to":   { "token": "EUR" },
    "estimated_time": "2-10 minutes"
  }
}
```

Card currency is determined by the cardholder's country: **UK** → GBP, **EU** → EUR, **all others** → USD.

***

## Step 5 — Onboard a Customer

Start the onboarding flow for each end customer. Only `email` is required.

```bash theme={null}
POST /v1/yativo-card/customers/onboard
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "email": "priya@yourapp.com",
  "external_customer_id": "usr_8821"
}
```

```json Response 201 theme={null}
{
  "success": true,
  "message": "Customer card onboarding initiated. Verification code sent to customer email.",
  "data": {
    "yativo_card_id": "yativo_card_customer_8f9a...abc_1769031332068",
    "customer_id": "6627f3a2c5d4e100123abcde",
    "external_customer_id": "usr_8821",
    "email_masked": "pr***@yourapp.com",
    "next_step": "verify_otp",
    "otp_expires_in_seconds": 600
  }
}
```

<Warning>
  Store **`yativo_card_id`** — it is the primary path parameter in every subsequent customer call. It is different from `customer_id`.

  Do **not** use your own issuer email as a customer email. Each customer must have a unique email address.
</Warning>

A 6-digit OTP is sent to the customer's email automatically.

***

## Step 6 — Verify OTP

Collect the OTP from your customer and submit it:

```bash theme={null}
POST /v1/yativo-card/customers/{yativoCardId}/verify-otp
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "otp": "847291"
}
```

```json Response 200 theme={null}
{
  "success": true,
  "message": "Email verified successfully",
  "data": {
    "yativo_card_id": "yativo_card_customer_8f9a...abc_1769031332068",
    "flow_status": "otp_verified",
    "next_step": "kyc_link"
  }
}
```

| Error code                | Meaning                                              | Recovery                                                 |
| ------------------------- | ---------------------------------------------------- | -------------------------------------------------------- |
| `OTP_EXPIRED`             | 10-minute window passed                              | Call `resend-otp`                                        |
| `OTP_INVALID_OR_EXPIRED`  | Wrong or expired code                                | Check code and retry, or call `resend-otp`               |
| `OTP_VERIFICATION_FAILED` | Verification service rejected with a specific reason | Inspect `message`, call `resend-otp`                     |
| `OTP_ATTEMPTS_EXCEEDED`   | 5 failed attempts                                    | Call `resend-otp` — this resets the counter              |
| `SESSION_EXPIRED`         | Session expired and couldn't auto-refresh            | Call `resend-otp` — a new OTP re-establishes the session |

Every error response with these codes includes a `data.next_step` field and a `data.action` URL pointing to the exact endpoint to call next.

### Resend OTP

```bash theme={null}
POST /v1/yativo-card/customers/{yativoCardId}/resend-otp
Authorization: Bearer YOUR_TOKEN
```

***

## Step 7 — Get KYC Link

Generate a verification URL for this customer:

```bash theme={null}
GET /v1/yativo-card/customers/{yativoCardId}/kyc-link
Authorization: Bearer YOUR_TOKEN
```

```json Response theme={null}
{
  "success": true,
  "message": "KYC link retrieved",
  "data": {
    "yativo_card_id": "yativo_card_customer_8f9a...abc_1769031332068",
    "kyc_url": "https://kyc.yativo.com/verify?token=...",
    "sumsub_sdk_token": "eyJ...",
    "next_step": "kyc_status"
  }
}
```

Redirect the customer to `kyc_url`, or embed the KYC flow in your app using the Sumsub SDK with `sumsub_sdk_token`. Add `?refresh=true` to generate a new link if the existing one has expired.

***

## Step 8 — Poll KYC Status

```bash theme={null}
GET /v1/yativo-card/customers/{yativoCardId}/kyc-status
Authorization: Bearer YOUR_TOKEN
```

```json Approved theme={null}
{
  "success": true,
  "data": {
    "yativo_card_id": "yativo_card_customer_8f9a...abc_1769031332068",
    "kyc_status": "approved",
    "flow_status": "kyc_completed",
    "terms_accepted": true,
    "next_step": "create_virtual_card"
  }
}
```

```json Pending theme={null}
{
  "success": true,
  "data": {
    "kyc_status": "pending",
    "flow_status": "kyc_initiated",
    "next_step": "kyc_status"
  }
}
```

```json Rejected theme={null}
{
  "success": true,
  "data": {
    "kyc_status": "rejected",
    "flow_status": "kyc_rejected",
    "rejection_reason": "Document not readable",
    "next_step": "retry_kyc"
  }
}
```

Poll every 10 seconds for up to 10 minutes. When `next_step` is `retry_kyc`, fetch a fresh `kyc-link?refresh=true` and redirect the customer.

<Warning>
  After KYC approval, you **must** call both of these endpoints before creating a card — regardless of whether the background operations appear to have succeeded:

  **1. Accept terms:**

  ```bash theme={null}
  POST /v1/yativo-card/{yativoCardId}/terms
  ```

  Works for both own-account and customer cards — supply the customer's `yativo_card_id` as the path parameter. This is idempotent. Skipping it causes `LEGAL_ACCEPTANCE_REQUIRED` at card creation.

  **2. Confirm Safe deployment:**

  ```bash theme={null}
  POST /v1/yativo-card/customers/{yativoCardId}/safe/deploy
  ```

  Even when the platform auto-deploys the Safe in the background, card creation checks the **local DB state**, not the card network directly. Calling this endpoint syncs the deployment status to the DB. You will typically receive `"already_deployed": true` (meaning the card network already has it — this call just ensures the DB record is updated). Skipping it causes `SAFE_NOT_DEPLOYED` at card creation.

  Both calls are idempotent. Call them in order after KYC approval, then proceed to Source of Funds.
</Warning>

***

## Step 9 — Source of Funds

### Get questions

```bash theme={null}
GET /v1/yativo-card/customers/{yativoCardId}/source-of-funds
Authorization: Bearer YOUR_TOKEN
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "questions": [
      {
        "question": "EMPLOYMENT_STATUS",
        "label": "What is your employment status?",
        "options": ["EMPLOYED", "SELF_EMPLOYED", "UNEMPLOYED", "STUDENT", "RETIRED"]
      },
      {
        "question": "SOURCE_OF_INCOME",
        "label": "What is your primary source of income?",
        "options": ["SALARY", "BUSINESS_INCOME", "INVESTMENT", "PENSION", "OTHER"]
      }
    ]
  }
}
```

### Submit answers

```bash theme={null}
POST /v1/yativo-card/customers/{yativoCardId}/source-of-funds
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "answers": [
    { "question": "EMPLOYMENT_STATUS", "answer": "EMPLOYED" },
    { "question": "SOURCE_OF_INCOME",  "answer": "SALARY" }
  ]
}
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "status": "completed",
    "next_step": "phone_verification"
  }
}
```

***

## Step 10 — Phone Verification

### Request SMS code

```bash theme={null}
POST /v1/yativo-card/customers/{yativoCardId}/phone/request-verification
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "phoneNumber": "+14155552671"
}
```

```json Response theme={null}
{
  "success": true,
  "message": "Verification code sent"
}
```

### Verify SMS code

```bash theme={null}
POST /v1/yativo-card/customers/{yativoCardId}/phone/verify
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "code": "382910"
}
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "is_verified": true,
    "next_step": "create_virtual_card"
  }
}
```

***

## Step 11 — Create Virtual Card

All prerequisites are complete. Issue the card:

```bash theme={null}
POST /v1/yativo-card/customers/{yativoCardId}/cards/virtual
Authorization: Bearer YOUR_TOKEN
```

```json Response 201 theme={null}
{
  "success": true,
  "message": "Virtual card created successfully",
  "data": {
    "yativo_card_id": "yativo_card_customer_8f9a...abc_1769031332068",
    "card_id": "afeb85fe-02f8-48da-b61e-84ad02704167",
    "card_type": "virtual",
    "status": "active",
    "status_code": 1000,
    "status_name": "Active",
    "activated_at": "2026-04-25T14:35:41.418Z",
    "total_cards": 1,
    "funding_wallet": {
      "address": "3vQ7GqN8kKTb2mFCdHrE8TvBcJgR2XpLmWqNzYsKdPh",
      "network": "solana",
      "asset": "USDC"
    }
  }
}
```

To show the customer their full card number and CVV, request a secure view URL (see [Secure Card View](#sensitive-card-data) below) and open it in an iframe or WebView.

### Multiple Cards per Customer

A single customer account supports up to **5 active cards** (virtual + physical combined). You can issue additional virtual cards on top of the first one, subject to the limits set on your program.

```bash theme={null}
POST /v1/yativo-card/customers/{yativoCardId}/cards/virtual
Authorization: Bearer YOUR_TOKEN
```

The response is identical to the first card creation. Each call issues a new independent card on the same customer account. If the customer has reached their limit you will get:

```json 403 Limit reached theme={null}
{
  "success": false,
  "error_code": "VIRTUAL_CARD_LIMIT_REACHED",
  "message": "Virtual card limit reached for this customer (max: 1). Contact your account manager to increase the limit."
}
```

### Card Limits

Limits work in a three-tier hierarchy:

```
Yativo Admin
  └── sets program ceiling (e.g. max 3 virtual per customer)
        └── You (the Issuer)
              └── can set per-customer override ≤ program ceiling
                    └── Platform hard cap: 5 active cards combined
```

The defaults on a new program are **1 virtual, 1 physical, 2 total** per customer.

#### Set per-customer limits (issuer)

Override the limit for a specific customer, up to your program's ceiling:

```bash theme={null}
PATCH /v1/yativo-card/customers/{yativoCardId}/card-limits
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "max_virtual": 2,
  "max_physical": 1,
  "max_total": 3
}
```

All fields are optional — send only the limits you want to change.

```json Response 200 theme={null}
{
  "success": true,
  "message": "Customer card limits updated",
  "data": {
    "yativo_card_id": "yativo_card_customer_8f9a...abc_1769031332068",
    "card_limits": {
      "max_virtual": 2,
      "max_physical": 1,
      "max_total": 3
    },
    "program_ceilings": {
      "max_virtual": 3,
      "max_physical": 2,
      "max_total": 5
    }
  }
}
```

`program_ceilings` reflects the maximum values your program permits. You cannot set a customer limit above its corresponding ceiling.

| Error code              | Meaning                                                                |
| ----------------------- | ---------------------------------------------------------------------- |
| `LIMIT_EXCEEDS_PROGRAM` | The value you sent is above your program's ceiling for that limit type |
| `NO_CHANGES`            | No valid limit fields were provided in the request body                |

***

## Step 12 — Fund the Card

### Option A: Push from Master Wallet *(master\_wallet programs)*

You can identify the customer by any of: `customer_id`, `card_id`, `external_id`, `email`, or `yativo_card_id`.

```bash theme={null}
POST /v1/card-issuer/fund-customer
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "external_id": "usr_8821",
  "amount": 100,
  "source_chain": "SOL",
  "pricing_mode": "receive_x"
}
```

```json Response 200 theme={null}
{
  "success": true,
  "message": "Funding initiated for customer",
  "data": {
    "transfer_id": "txn_01HX9KZMB3F7VNQP8R2WDGT4E5",
    "status": "pending",
    "source": {
      "chain": "SOL",
      "amount": 201.80
    },
    "destination": {
      "token": "EUR"
    },
    "pricing_mode": "receive_x",
    "estimated_amount": "200.00",
    "estimated_time": "2–5 minutes"
  }
}
```

Save the `transfer_id`. Poll [GET /v1/card-issuer/transfers/:transferId](/api-reference/issuer/transfer-status) until `status` is `completed` (typically 2–5 minutes).

**`pricing_mode`:**

* `receive_x` — the customer receives exactly `amount`. Yativo deducts slightly more from your master wallet to cover fees.
* `send_x` *(default)* — exactly `amount` is debited from your master wallet. The customer receives less after fees.

**`TOKEN_MISMATCH` error:** if you fund with the wrong currency for a customer's card region, the API returns this error with the exact token their card requires. Swap your master wallet balance first.

### Option B: Customer Self-Funds via Deposit Address *(non\_master programs)*

Get the customer's deposit address:

```bash theme={null}
GET /v1/yativo-card/customers/{yativoCardId}/wallet/funding-address
Authorization: Bearer YOUR_TOKEN
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "address": "3vQ7GqN8kKTb2mFCdHrE8TvBcJgR2XpLmWqNzYsKdPh",
    "network": "solana",
    "card_currency": "USDCe",
    "minimum_deposit": "5.00",
    "funding_sources": {
      "usdc_sol": {
        "address": "3vQ7GqN8kKTb2mFCdHrE8TvBcJgR2XpLmWqNzYsKdPh",
        "network": "solana",
        "token": "USDC",
        "contract": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
        "bridge_type": "automatic",
        "bridge_time": "2-5 minutes",
        "description": "Send USDC on Solana — automatically applied to your card"
      },
      "usdc_xdc": {
        "address": "0x742d35Cc6634C0532925a3b8D4C9b98E8A1f4bC2",
        "network": "xdc",
        "token": "USDC",
        "contract": "0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1",
        "bridge_type": "manual",
        "bridge_time": "15-25 minutes",
        "description": "No minimum. Hold to earn yield. Fund card on demand.",
        "balance": { "total": 0, "available": 0, "held": 0 }
      }
    },
    "auto_bridge": {
      "enabled": true,
      "source": "USDC_SOL",
      "destination": "USDC",
      "estimated_time": "2-5 minutes",
      "minimum_trigger_amount": 5,
      "minimum_trigger_amount_unit": "USDC",
      "accumulates_below_minimum": true
    },
    "instructions": "Send USDC (SPL) on Solana to this address. Auto-bridge starts once your available balance reaches at least 5.00 USDC."
  }
}
```

***

## Card Management

### List all customers

```bash theme={null}
GET /v1/card-issuer/customers?status=card_created&page=1&limit=20
Authorization: Bearer YOUR_TOKEN
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "customers": [
      {
        "yativo_card_id": "yativo_card_customer_8f9a...abc_1769031332068",
        "customer_id": "6627f3a2c5d4e100123abcde",
        "external_customer_id": "usr_8821",
        "email": "priya@yourapp.com",
        "flow_status": "card_created",
        "currency": "USDCe",
        "cards_count": 1,
        "created_at": "2026-04-25T10:00:00.000Z"
      }
    ],
    "total": 1,
    "page": 1,
    "limit": 20
  }
}
```

Filter by `flow_status`: `otp_requested`, `otp_verified`, `kyc_initiated`, `kyc_completed`, `card_created`, `active`.

### Look up a customer

Find a customer by any identifier:

```bash theme={null}
GET /v1/card-issuer/lookup-customer?external_id=usr_8821
Authorization: Bearer YOUR_TOKEN
```

Supported query parameters (pass exactly one):

| Param            | Description                                                 |
| ---------------- | ----------------------------------------------------------- |
| `yativo_card_id` | Yativo's internal card customer ID                          |
| `customer_id`    | MongoDB customer ObjectId (or `yativo_card_id` as fallback) |
| `card_id`        | Card ID from card creation                                  |
| `external_id`    | Your reference ID set at onboarding                         |
| `email`          | Customer email address                                      |
| `id`             | MongoDB `_id` of the customer record                        |

```json Response theme={null}
{
  "success": true,
  "data": {
    "yativo_card_id": "yativo_card_customer_8f9a...abc_1769031332068",
    "external_id": "usr_8821",
    "email": "priya@yourapp.com",
    "flow_status": "card_created",
    "currency": "USDCe",
    "token": "USDC",
    "safe_deployed": true,
    "cards_count": 1,
    "created_at": "2026-04-25T10:00:00.000Z"
  }
}
```

### Freeze / Unfreeze a Card

```bash theme={null}
POST /v1/yativo-card/customers/{yativoCardId}/cards/{cardId}/freeze
Authorization: Bearer YOUR_TOKEN
```

```json Response theme={null}
{
  "success": true,
  "message": "Card frozen successfully",
  "data": {
    "yativo_card_id": "yativo_card_customer_8f9a...abc_1769031332068",
    "card_id": "afeb85fe-02f8-48da-b61e-84ad02704167",
    "is_frozen": true,
    "frozen_at": "2026-04-25T19:07:14.917Z"
  }
}
```

```bash theme={null}
POST /v1/yativo-card/customers/{yativoCardId}/cards/{cardId}/unfreeze
Authorization: Bearer YOUR_TOKEN
```

```json Response theme={null}
{
  "success": true,
  "message": "Card unfrozen successfully",
  "data": {
    "yativo_card_id": "yativo_card_customer_8f9a...abc_1769031332068",
    "card_id": "afeb85fe-02f8-48da-b61e-84ad02704167",
    "is_frozen": false,
    "unfrozen_at": "2026-04-25T19:07:20.065Z"
  }
}
```

### Get Card Transactions

```bash theme={null}
GET /v1/yativo-card/customers/{yativoCardId}/cards/{cardId}/transactions?limit=20
Authorization: Bearer YOUR_TOKEN
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "transactions": [
      {
        "kind": "Payment",
        "transaction_type": "Purchase",
        "merchant_name": "CloudSoft Inc",
        "merchant_country": "United States",
        "merchant_country_code": "US",
        "mcc_code": "5734",
        "mcc_description": "Software",
        "mcc_category": "Electronics & Tech",
        "billing_amount": 13.72,
        "billing_currency": "USD",
        "billing_formatted": "$13.72",
        "transaction_amount": 13.72,
        "transaction_currency": "USD",
        "status": "approved",
        "status_label": "Approved",
        "is_pending": false,
        "card_token": "849301752",
        "authorized_at": "2026-04-22T15:03:31.441Z",
        "cleared_at": "2026-04-24T15:36:51.331Z"
      }
    ]
  }
}
```

### Spending Limits

Get the current spending limit for a customer's card:

```bash theme={null}
GET /v1/yativo-card/customers/{yativoCardId}/wallet/limits
Authorization: Bearer YOUR_TOKEN
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "currency": "USD",
    "limits": {
      "daily": {
        "limit": 7999,
        "used": 0,
        "remaining": 7999
      },
      "monthly": {},
      "per_transaction": {}
    }
  }
}
```

Update the daily spending limit:

```bash theme={null}
PUT /v1/yativo-card/customers/{yativoCardId}/wallet/limits
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "dailyLimit": 2500
}
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "current_limit": null,
    "requested_limit": 2500,
    "admin_limit": null,
    "delay_seconds": 180,
    "note": "Limit changes are processed through a delay relay. Refresh after 3 minutes to see the updated limit.",
    "gelato_status": "ExecSuccess",
    "enqueue_task_id": "0xb3f4901cd12347834fe93cc55b6608e1ff81gddd63g9d5771e5b5f161d6f194fe"
  }
}
```

<Note>
  Limit changes go through a Gelato relay with a 3-minute processing delay. The `gelato_status: "ExecSuccess"` confirms the transaction was enqueued. Poll `GET /wallet/limits` after 3 minutes to confirm the new limit is active.
</Note>

***

## Secure Card View

To let a cardholder view their full card details (PAN, CVV, expiry, PIN), your backend requests a **secure view URL** from Yativo. You send that URL to your customer — via your app, an email link, or an in-app iframe. Yativo hosts the page and handles all cryptography internally. Your backend never sees or handles raw card data.

### How it works

```
Your backend  →  POST view-token  →  gets secure_view_url
Your app      →  opens URL in iframe / WebView / browser tab
Yativo page   →  renders secure card data page
Cardholder    →  sees PAN, CVV, expiry (and optionally PIN)
```

No SDK integration is required on your side. Yativo handles all the cryptography and secure rendering.

### Request a secure view URL

```bash theme={null}
POST /v1/yativo-card/customers/{yativoCardId}/cards/{cardId}/view-token
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "enabled_views": ["data", "pin"],
  "require_access_code": true,
  "access_code": "1234",
  "theme": {
    "accent_color": "#6366f1",
    "background_color": "#ffffff",
    "logo_url": "https://yourapp.com/logo.png",
    "border_radius": 12
  }
}
```

| Field                    | Type    | Description                                                                 |
| ------------------------ | ------- | --------------------------------------------------------------------------- |
| `enabled_views`          | array   | Which panels to show: `"data"` (PAN/CVV/expiry), `"pin"`. Defaults to both. |
| `require_access_code`    | boolean | Require the cardholder to enter a code before the card data unlocks.        |
| `access_code`            | string  | The code the cardholder must enter (set by you, shared via your app).       |
| `theme.accent_color`     | string  | Hex color for buttons and highlights.                                       |
| `theme.background_color` | string  | Page background color.                                                      |
| `theme.panel_color`      | string  | Card panel background color.                                                |
| `theme.text_color`       | string  | Primary text color.                                                         |
| `theme.logo_url`         | string  | Your logo shown at the top of the page.                                     |
| `theme.border_radius`    | number  | Corner radius in px (8–36).                                                 |
| `theme.font_family`      | string  | CSS font-family string.                                                     |

```json Response theme={null}
{
  "success": true,
  "data": {
    "secure_view_url": "https://crypto-api.yativo.com/api/v1/yativo-card/view/eyJhbGci...",
    "expires_at": "2026-04-25T19:09:00.000Z",
    "last_four": "4291",
    "card_type": "virtual",
    "holder_name": "John Doe",
    "pin_set": true,
    "enabled_views": ["data", "pin"],
    "requires_access_code": true,
    "usage_notes": [
      "Open secure_view_url in a browser, iframe, or WebView hosted on your side",
      "The hosted page renders card data securely and does not auto-refresh",
      "Request a fresh view URL whenever the previous one expires"
    ]
  }
}
```

Open `secure_view_url` in a browser tab, iframe, or mobile WebView. The URL is short-lived. Request a new one each time the customer wants to view their card.

The response includes `pin_set` — the last known state from the `physical.card.pin.changed` webhook. Use it in your UI to decide whether to prompt the customer to set their PIN before showing card details.

### Customer PIN setup and change

Every card starts with `pin_set: false`. The customer must set a PIN before chip-and-PIN or ATM transactions will work. Use the same view-token endpoint with `enabled_views: ["pin"]` to show the PIN setup page — this also handles PIN **changes**.

```bash theme={null}
POST /v1/yativo-card/customers/{yativoCardId}/cards/{cardId}/view-token
Authorization: Bearer YOUR_ISSUER_TOKEN
Content-Type: application/json

{
  "enabled_views": ["pin"],
  "theme": {
    "accent_color": "#6366f1",
    "logo_url": "https://yourapp.com/logo.png"
  }
}
```

Open the returned `secure_view_url` for your customer in an iframe or WebView. The cardholder enters their 4-digit PIN directly in the hosted page — it never passes through your backend. Once saved, the card network fires a `physical.card.pin.changed` webhook and Yativo updates `pin_set: true` on the card record.

<Note>
  `pin_set` is sourced from the webhook, not from the Gnosis API. Use it as a UI hint to prompt customers who haven't yet set a PIN — not as a hard access gate.
</Note>

***

## Deposits

View all funding deposits into your issuer program (SOL auto-funding and XDC manual deposits):

```bash theme={null}
GET /v1/card-issuer/deposits?page=1&limit=20
Authorization: Bearer YOUR_TOKEN
```

Filter by `status`: `pending`, `auto_funding`, `awaiting_manual`, `funding`, `funded`, `failed`.
Filter by `chain`: `SOL`, `XDC`.

***

## Transfers

### All transfers

View all funding transfers you have sent to customer cards:

```bash theme={null}
GET /v1/card-issuer/transfers?page=1&limit=20
Authorization: Bearer YOUR_TOKEN
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "transfers": [
      {
        "transfer_id": "txn_01HX9KZMB3F7VNQP8R2WDGT4E5",
        "source_chain": "SOL",
        "source_amount": 201.80,
        "destination_token": "EUR",
        "destination_amount": 200.00,
        "status": "completed",
        "customer_id": "6627f3a2c5d4e100123abcde",
        "created_at": "2026-04-25T12:00:00.000Z",
        "completed_at": "2026-04-25T12:08:42.000Z"
      }
    ],
    "total": 1,
    "page": 1,
    "limit": 20
  }
}
```

Supports `status`, `customer_id`, and `chain` query filters. See [List Transfers](/api-reference/issuer/transfers).

### Single transfer status

```bash theme={null}
GET /v1/card-issuer/transfers/{transferId}
Authorization: Bearer YOUR_TOKEN
```

See [Get Transfer Status](/api-reference/issuer/transfer-status) for the full response shape and status enum.

### Transfers for a specific customer

```bash theme={null}
GET /v1/card-issuer/customers/{customerId}/transfers
Authorization: Bearer YOUR_TOKEN
```

`customerId` accepts `yativo_card_id`, `customer_id`, or your own `external_id`. See [List Customer Transfers](/api-reference/issuer/customer-transfers).

***

## Check Progress Anytime

```bash theme={null}
GET /v1/yativo-card/customers/{yativoCardId}/progress
Authorization: Bearer YOUR_TOKEN
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "yativo_card_id": "yativo_card_customer_8f9a...abc_1769031332068",
    "flow_status": "card_created",
    "current_step": 5,
    "progress_percentage": 100,
    "steps": [
      { "step": 1, "name": "Initiate Program",  "completed": true },
      { "step": 2, "name": "Verify Email",       "completed": true },
      { "step": 3, "name": "Start KYC",          "completed": true },
      { "step": 4, "name": "Complete KYC",        "completed": true },
      { "step": 5, "name": "Create Card",         "completed": true }
    ],
    "kyc_status": "approved",
    "card_status": "active"
  }
}
```

***

## Delete an Incomplete Customer Record

If you created a customer by mistake (e.g. wrong email) and the card has not yet been issued:

```bash theme={null}
DELETE /v1/card-issuer/customers/{yativoCardId}
Authorization: Bearer YOUR_TOKEN
```

This endpoint is blocked once the customer reaches `card_created` or `active` status.

***

## Reset Customer Onboarding

If a customer's onboarding is stuck (KYC rejected multiple times, verification loop, wrong details submitted) and you need to start completely fresh, use the reset endpoint. This deletes the Yativo record and allows re-onboarding with a new verification flow.

```bash theme={null}
POST /v1/yativo-card/customers/{yativoCardId}/reset
Authorization: Bearer YOUR_TOKEN
```

```json Response 200 theme={null}
{
  "success": true,
  "message": "Customer onboarding reset. Re-onboard this customer via POST /api/yativo-card/customers/onboard with a different email address.",
  "data": {
    "deleted_card_id": "yativo_card_customer_8f9a...abc_1769031332068",
    "email_masked": "sa***@hotmail.com",
    "external_customer_id": "usr_8821",
    "next_step": "POST /api/yativo-card/customers/onboard",
    "email_warning": "The email address used previously remains registered with the card network and cannot be reused. Use a different email address when re-onboarding this customer."
  }
}
```

<Warning>
  **You must use a different email address when re-onboarding.** The previous email remains registered with the card network and cannot be reused for a new account. Attempting to re-onboard with the same email will fail with `EMAIL_ALREADY_REGISTERED`.
</Warning>

**When reset is blocked:**

As an issuer, you can only reset a customer who has **not yet had a card issued**. Once the customer reaches `card_created` status, this endpoint returns:

```json 400 Card already issued theme={null}
{
  "success": false,
  "error_code": "CARD_ALREADY_ISSUED",
  "message": "Cannot reset: a payment card has already been issued for this customer. Contact support."
}
```

If you need to reset a customer who already has a card, contact Yativo support — admin-level resets are available for exceptional cases.

***

## Webhook Events

Subscribe to webhooks for real-time events. See [Webhook Events](/api-reference/issuer/webhooks) for payload examples, signature verification, and the full event reference.

| Event                           | Trigger                                                                                         |
| ------------------------------- | ----------------------------------------------------------------------------------------------- |
| `customer.funded`               | Funding transfer completed into a customer's card wallet                                        |
| `customer.funding.failed`       | Funding transfer failed                                                                         |
| `master_wallet.customer_funded` | Master wallet debited for a customer funding (includes `remaining_balance` on direct transfers) |
| `master_wallet.deposit`         | Funds received into your master wallet                                                          |
| `master_wallet.swap`            | Token swap submitted from your master wallet                                                    |
| `card.created`                  | New card issued to a customer                                                                   |
| `card.activated`                | Physical card activated                                                                         |
| `card.frozen`                   | Card frozen                                                                                     |
| `card.unfrozen`                 | Card unfrozen                                                                                   |
| `card.voided`                   | Card permanently voided                                                                         |
| `card.lost`                     | Card reported lost                                                                              |
| `card.stolen`                   | Card reported stolen                                                                            |
| `card.cancelled`                | Card cancelled                                                                                  |
| `transaction.authorized`        | Card transaction authorized at point of sale                                                    |
| `transaction.settled`           | Transaction cleared and settled                                                                 |
| `transaction.declined`          | Transaction declined                                                                            |
| `transaction.reversed`          | Transaction reversed                                                                            |
| `transaction.refund.created`    | Refund created                                                                                  |
| `customer.balance.updated`      | Customer balance changed after a spend, top-up, or authorization                                |

<Note>
  KYC status changes are not delivered as webhooks — poll [Get Customer](/api-reference/issuer/customer) or [Look Up Customer](/api-reference/issuer/lookup-customer) to check `kyc_status` after directing customers through the KYC link.
</Note>

***

***

## Recovery & Continuation Reference

Every operation that can fail returns a `data.next_step` field and a `data.action` URL so you never have to guess what to do. This section documents every failure mode and the exact API call that recovers from it.

### Onboarding State Machine

```
[new customer]
      │
      ▼  POST /customers/onboard
 otp_requested ──────► resend-otp (OTP expired / too many attempts / session expired)
      │
      ▼  POST /customers/{id}/verify-otp
 otp_verified
      │
      ▼  GET /customers/{id}/kyc-link
 kyc_initiated ──────► GET /customers/{id}/kyc-link?refresh=true  (link expired)
      │
      ├─► (pending) ── poll kyc-status every 10s
      │
      ├─► kyc_rejected ──► GET /customers/{id}/kyc-link?refresh=true → retry KYC
      │
      └─► kyc_completed
              │
              ▼  POST /v1/yativo-card/{id}/terms  (idempotent — always call after KYC)
         terms_accepted
              │
              ▼  POST /customers/{id}/safe/deploy  (idempotent — syncs DB state)
         safe_deployed
              │
              ▼  GET /customers/{id}/source-of-funds → POST /customers/{id}/source-of-funds
      source_of_funds_completed
              │
              ▼  POST /customers/{id}/phone/request-verification
                 POST /customers/{id}/phone/verify
         phone_verified
              │
              ▼  POST /customers/{id}/cards/virtual
          card_created
              │
              ▼
           active  (terminal — no further steps)
```

***

### Handling All Error Codes

#### OTP Phase errors

| Error code                | Cause                                             | Recovery call                                                          |
| ------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------- |
| `OTP_EXPIRED`             | 10-min window elapsed                             | `POST /customers/{id}/resend-otp`                                      |
| `OTP_INVALID_OR_EXPIRED`  | Wrong or expired code                             | Retry with correct code, or `POST /customers/{id}/resend-otp`          |
| `OTP_VERIFICATION_FAILED` | Verification service returned an unexpected error | Inspect `message` field; then `resend-otp`                             |
| `OTP_ATTEMPTS_EXCEEDED`   | 5 wrong attempts                                  | `POST /customers/{id}/resend-otp` — **this resets the counter**        |
| `SESSION_EXPIRED`         | JWT expired and SIWE auto-refresh failed          | `POST /customers/{id}/resend-otp` — new OTP re-establishes the session |
| `JWT_REFRESH_FAILED`      | SIWE re-auth failed (rare)                        | `POST /customers/{id}/resend-otp`                                      |

All OTP errors include `data.attempts_remaining`, `data.next_step: "resend_otp"`, and `data.action` in the response body.

#### KYC Phase errors

| Error code         | Cause                                             | Recovery call                                                                       |
| ------------------ | ------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `KYC_LINK_FAILED`  | Identity verification link could not be generated | Retry `GET /customers/{id}/kyc-link?refresh=true` after a short delay               |
| `KYC_STATUS_ERROR` | Live status check failed                          | Retry `GET /customers/{id}/kyc-status` — response falls back to cached status       |
| `SESSION_EXPIRED`  | JWT expired and can't refresh                     | `GET /customers/{id}/kyc-link?refresh=true` — this forces a SIWE re-auth internally |

***

### Abandoned Onboardings — `resume` Endpoint

If a customer starts onboarding but never finishes (OTP not verified, KYC link never opened, KYC in progress for days), call the **resume** endpoint. It inspects the current state, takes the correct action automatically, and tells you exactly what to present to the customer next.

```bash theme={null}
POST /v1/yativo-card/customers/{yativoCardId}/resume
Authorization: Bearer YOUR_TOKEN
```

**What it does by state:**

| `flow_status`                                      | Action taken                                          | `next_step` returned  |
| -------------------------------------------------- | ----------------------------------------------------- | --------------------- |
| `otp_requested`                                    | Resends OTP to customer email, resets attempt counter | `verify_otp`          |
| `reauth_otp_requested`                             | Same as above (re-auth OTP pending)                   | `verify_otp`          |
| `otp_verified`                                     | Generates a fresh KYC link                            | `kyc_link`            |
| `kyc_initiated`                                    | Generates a fresh KYC link                            | `kyc_link`            |
| `kyc_rejected`                                     | Generates a fresh KYC link for retry                  | `retry_kyc`           |
| `kyc_completed` / `kyc_approved` / `safe_deployed` | No action needed — ready for card creation            | `create_virtual_card` |
| `card_created` / `active`                          | Already complete                                      | `none`                |

```json Response — OTP phase theme={null}
{
  "success": true,
  "message": "OTP resent to customer email. Customer should verify to continue.",
  "data": {
    "yativo_card_id": "yativo_card_customer_...",
    "flow_status": "otp_requested",
    "next_step": "verify_otp",
    "action_taken": "otp_resent",
    "email_masked": "sa***@truther.to",
    "otp_expires_in_seconds": 600,
    "days_since_created": 6
  }
}
```

```json Response — KYC phase theme={null}
{
  "success": true,
  "message": "KYC was rejected. A new KYC link has been generated for retry.",
  "data": {
    "yativo_card_id": "yativo_card_customer_...",
    "flow_status": "kyc_rejected",
    "kyc_status": "rejected",
    "rejection_reason": "Document not readable",
    "next_step": "retry_kyc",
    "action_taken": "kyc_link_generated",
    "kyc_url": "https://kyc.yativo.com/verify?token=...",
    "sumsub_sdk_token": "eyJ...",
    "days_since_created": 2
  }
}
```

<Tip>
  Use `days_since_created` to decide how aggressively to pursue a customer. A 1-day abandoned onboarding is worth a simple email nudge; a 30-day one may warrant re-onboarding from scratch.
</Tip>

***

### Polling Strategy

For KYC status polling, use an exponential-backoff approach with a hard timeout:

```typescript theme={null}
async function waitForKycApproval(yativoCardId: string, token: string): Promise<string> {
  const maxWait = 15 * 60 * 1000; // 15 minutes
  const start = Date.now();
  let delay = 5_000; // start at 5s

  while (Date.now() - start < maxWait) {
    const res = await fetch(`/v1/yativo-card/customers/${yativoCardId}/kyc-status`, {
      headers: { Authorization: `Bearer ${token}` }
    });
    const body = await res.json();
    const status = body.data?.kyc_status;

    if (status === 'approved') return 'approved';
    if (status === 'rejected') return 'rejected';

    await new Promise(r => setTimeout(r, delay));
    delay = Math.min(delay * 1.5, 30_000); // cap at 30s
  }
  return 'timeout';
}
```

When `kyc_status === 'rejected'`, fetch a fresh KYC link and redirect the customer:

```typescript theme={null}
if (kycResult === 'rejected') {
  const linkRes = await fetch(
    `/v1/yativo-card/customers/${yativoCardId}/kyc-link?refresh=true`,
    { headers: { Authorization: `Bearer ${token}` } }
  );
  const { data } = await linkRes.json();
  redirectCustomer(data.kyc_url); // or embed Sumsub SDK with data.sumsub_sdk_token
}
```

***

### Full `next_step` Reference

Every API response in the customer lifecycle includes `data.next_step`. Use it to drive your state machine without hardcoding assumptions about flow order.

| `next_step` value     | What to do                                                                                                                        |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `verify_otp`          | Show OTP input to customer                                                                                                        |
| `resend_otp`          | OTP is unusable — call `resend-otp` then show OTP input                                                                           |
| `kyc_link`            | Open `kyc_url` or embed SDK                                                                                                       |
| `kyc_status`          | Start polling `kyc-status`                                                                                                        |
| `retry_kyc`           | KYC rejected — show reason, get new link, redirect customer                                                                       |
| `create_virtual_card` | KYC approved — complete remaining steps (terms, safe deploy, source of funds, phone verification) then call `POST /cards/virtual` |
| `none`                | Customer is fully active, no further action                                                                                       |
| `null`                | Intermediate state — call `GET /progress` for full picture                                                                        |

***

### Common Integration Mistakes

<Warning>
  **Do not hardcode state transitions.** Always read `next_step` from the response. States can be added between releases.
</Warning>

| Mistake                                                   | Consequence                                       | Fix                                                                                                                                                          |
| --------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Using issuer email as customer email                      | `ISSUER_EMAIL_NOT_ALLOWED` — blocked at onboard   | Use the customer's own email address                                                                                                                         |
| Not storing `yativo_card_id`                              | Can't call any subsequent customer endpoint       | Store it immediately after `POST /customers/onboard`                                                                                                         |
| Treating `OTP_ATTEMPTS_EXCEEDED` as fatal                 | Customer stuck                                    | Call `resend-otp` — resets the counter                                                                                                                       |
| Not handling `kyc_rejected`                               | Customer stuck in KYC loop                        | Detect rejection in `kyc-status`, fetch fresh link, re-direct                                                                                                |
| Re-creating customer after OTP failure                    | Duplicate records                                 | Call `resend-otp` on the existing `yativo_card_id`                                                                                                           |
| Calling `kyc-link` for `otp_requested` customers          | `SESSION_EXPIRED` or wrong step                   | Ensure OTP is verified before requesting KYC link                                                                                                            |
| Skipping explicit Safe deploy call after KYC              | `SAFE_NOT_DEPLOYED` at card creation              | Auto-deploy is async via background job and may not update the DB before card creation — call `POST /customers/{id}/safe/deploy` explicitly to sync DB state |
| Card creation fails with `LEGAL_ACCEPTANCE_REQUIRED`      | Terms auto-accept failed silently at KYC approval | Call `POST /v1/yativo-card/{yativoCardId}/terms` using the customer's `yativo_card_id` — works for customer cards, idempotent                                |
| Treating `INITIALIZATION_FAILED` as a no-op on first call | Customer never progresses                         | See below — the record may have been created. Always retry and check for `CUSTOMER_CARD_ALREADY_ACTIVE`.                                                     |

***

### Handling `INITIALIZATION_FAILED` on Onboard

`POST /customers/onboard` performs several steps internally (wallet generation, SIWE signing, OTP request, DB write). In rare cases — typically a transient database timeout or network issue — the API returns `500 INITIALIZATION_FAILED` even though the customer record **was** successfully created and the OTP email was already sent.

**Recovery pattern:** Retry the onboard call with the same email (and `external_customer_id` if you use one). One of two things will happen:

1. **Success (201)** — the first call truly failed end-to-end. A fresh customer record is created.
2. **`CUSTOMER_CARD_ALREADY_ACTIVE` (409)** — the first call partially succeeded. The response includes the `yativo_card_id` and `next_step` so you can pick up exactly where you left off:

```json 409 CUSTOMER_CARD_ALREADY_ACTIVE theme={null}
{
  "success": false,
  "error_code": "CUSTOMER_CARD_ALREADY_ACTIVE",
  "data": {
    "yativo_card_id": "yativo_card_customer_8f1f..._1781875244343",
    "flow_status": "otp_requested",
    "next_step": "verify_otp",
    "external_customer_id": "6126"
  }
}
```

Use the returned `yativo_card_id` to call `POST /customers/{yativoCardId}/verify-otp` (the OTP email was already delivered on the first call). Do **not** treat `CUSTOMER_CARD_ALREADY_ACTIVE` as an error — it is the system telling you the card exists and showing you how to continue.

***

## Quick Reference

### Issuer Program (`/v1/card-issuer/*`)

| Action                        | Method   | Path                                       |
| ----------------------------- | -------- | ------------------------------------------ |
| Check eligibility             | `GET`    | `/v1/card-issuer/eligibility`              |
| Apply                         | `POST`   | `/v1/card-issuer/apply`                    |
| Check program status          | `GET`    | `/v1/card-issuer/status`                   |
| Get master wallet balances    | `GET`    | `/v1/card-issuer/master-wallets`           |
| Swap master wallet currencies | `POST`   | `/v1/card-issuer/master-wallets/swap`      |
| List customers                | `GET`    | `/v1/card-issuer/customers`                |
| Look up customer              | `GET`    | `/v1/card-issuer/lookup-customer`          |
| Fund a customer card          | `POST`   | `/v1/card-issuer/fund-customer`            |
| List all transfers            | `GET`    | `/v1/card-issuer/transfers`                |
| Get transfer status           | `GET`    | `/v1/card-issuer/transfers/{transferId}`   |
| List customer transfers       | `GET`    | `/v1/card-issuer/customers/{id}/transfers` |
| Deposits list                 | `GET`    | `/v1/card-issuer/deposits`                 |
| Delete incomplete customer    | `DELETE` | `/v1/card-issuer/customers/{id}`           |

### Card Customer Lifecycle (`/v1/yativo-card/customers/*`)

| Action                              | Method  | Path                                                         |
| ----------------------------------- | ------- | ------------------------------------------------------------ |
| Onboard customer                    | `POST`  | `/v1/yativo-card/customers/onboard`                          |
| Verify OTP                          | `POST`  | `/v1/yativo-card/customers/{id}/verify-otp`                  |
| Resend OTP                          | `POST`  | `/v1/yativo-card/customers/{id}/resend-otp`                  |
| **Resume stuck onboarding**         | `POST`  | `/v1/yativo-card/customers/{id}/resume`                      |
| Get KYC link                        | `GET`   | `/v1/yativo-card/customers/{id}/kyc-link`                    |
| Poll KYC status                     | `GET`   | `/v1/yativo-card/customers/{id}/kyc-status`                  |
| **Deploy Safe** (sync DB after KYC) | `POST`  | `/v1/yativo-card/customers/{id}/safe/deploy`                 |
| **Accept terms** (after KYC)        | `POST`  | `/v1/yativo-card/{yativoCardId}/terms`                       |
| Get SoF questions                   | `GET`   | `/v1/yativo-card/customers/{id}/source-of-funds`             |
| Submit SoF answers                  | `POST`  | `/v1/yativo-card/customers/{id}/source-of-funds`             |
| Request phone OTP                   | `POST`  | `/v1/yativo-card/customers/{id}/phone/request-verification`  |
| Verify phone OTP                    | `POST`  | `/v1/yativo-card/customers/{id}/phone/verify`                |
| Create virtual card                 | `POST`  | `/v1/yativo-card/customers/{id}/cards/virtual`               |
| **Set per-customer card limits**    | `PATCH` | `/v1/yativo-card/customers/{id}/card-limits`                 |
| **Reset customer onboarding**       | `POST`  | `/v1/yativo-card/customers/{id}/reset`                       |
| Get deposit address                 | `GET`   | `/v1/yativo-card/customers/{id}/wallet/funding-address`      |
| Check onboarding progress           | `GET`   | `/v1/yativo-card/customers/{id}/progress`                    |
| Freeze card                         | `POST`  | `/v1/yativo-card/customers/{id}/cards/{cardId}/freeze`       |
| Unfreeze card                       | `POST`  | `/v1/yativo-card/customers/{id}/cards/{cardId}/unfreeze`     |
| Void card (virtual only)            | `POST`  | `/v1/yativo-card/customers/{id}/cards/{cardId}/void`         |
| Report card lost                    | `POST`  | `/v1/yativo-card/customers/{id}/cards/{cardId}/lost`         |
| Report card stolen                  | `POST`  | `/v1/yativo-card/customers/{id}/cards/{cardId}/stolen`       |
| Get transactions                    | `GET`   | `/v1/yativo-card/customers/{id}/cards/{cardId}/transactions` |
| Get spending limits                 | `GET`   | `/v1/yativo-card/customers/{id}/wallet/limits`               |
| Set spending limit                  | `PUT`   | `/v1/yativo-card/customers/{id}/wallet/limits`               |
| Get secure card view URL            | `POST`  | `/v1/yativo-card/customers/{id}/cards/{cardId}/view-token`   |
| Get customer profile                | `GET`   | `/v1/yativo-card/customers/{id}/profile`                     |
