Skip to main content

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.

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.
Two API namespaces — keep them separate.
NamespaceBase pathPurpose
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.

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.
GET /v1/card-issuer/eligibility
Authorization: Bearer YOUR_TOKEN
Response
{
  "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

POST /v1/card-issuer/apply
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "funding_structure": "master_wallet",
  "preferred_chain": "SOL",
  "iban_enabled": false
}
FieldTypeRequiredDescription
funding_structurestringYesmaster_wallet or non_master (see below)
preferred_chainstringNoSOL (default) or XDC — the chain your master wallet will receive funds on
iban_enabledbooleanNoEnable IBAN for cardholders. Defaults to false.

Funding Structures

ValueHow it works
master_walletYou 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_masterEach customer receives their own deposit address and funds their card directly. Best for self-serve wallets.
Response
{
  "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.
GET /v1/card-issuer/status
Authorization: Bearer YOUR_TOKEN
Approved
{
  "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
    }
  }
}
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.

Step 4 — Fund Your Master Wallet (master_wallet programs only)

Get your current balances and deposit addresses:
GET /v1/card-issuer/master-wallets
Authorization: Bearer YOUR_TOKEN
Response
{
  "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:
POST /v1/card-issuer/master-wallets/swap
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "from_token": "USD",
  "to_token": "EUR",
  "amount": 1000
}
Response
{
  "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.
POST /v1/yativo-card/customers/onboard
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "email": "priya@yourapp.com",
  "external_customer_id": "usr_8821"
}
Response 201
{
  "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": "priya@yourapp.com",
    "next_step": "verify_otp",
    "otp_expires_in_seconds": 600
  }
}
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.
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:
POST /v1/yativo-card/customers/{yativoCardId}/verify-otp
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "otp": "847291"
}
Response 200
{
  "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 codeMeaningRecovery
OTP_EXPIRED10-minute window passedCall resend-otp
OTP_INVALID_OR_EXPIREDWrong or expired codeCheck code and retry, or call resend-otp
OTP_VERIFICATION_FAILEDVerification service rejected with a specific reasonInspect message, call resend-otp
OTP_ATTEMPTS_EXCEEDED5 failed attemptsCall resend-otp — this resets the counter
SESSION_EXPIREDSession expired and couldn’t auto-refreshCall 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

POST /v1/yativo-card/customers/{yativoCardId}/resend-otp
Authorization: Bearer YOUR_TOKEN

Generate a verification URL for this customer:
GET /v1/yativo-card/customers/{yativoCardId}/kyc-link
Authorization: Bearer YOUR_TOKEN
Response
{
  "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

GET /v1/yativo-card/customers/{yativoCardId}/kyc-status
Authorization: Bearer YOUR_TOKEN
Approved
{
  "success": true,
  "data": {
    "yativo_card_id": "yativo_card_customer_8f9a...abc_1769031332068",
    "kyc_status": "approved",
    "flow_status": "kyc_completed",
    "terms_accepted": true,
    "next_step": "source_of_funds"
  }
}
Pending
{
  "success": true,
  "data": {
    "kyc_status": "pending",
    "flow_status": "kyc_initiated",
    "next_step": "kyc_status"
  }
}
Rejected
{
  "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.
When KYC transitions to approved, Yativo automatically provisions the customer’s card wallet in the background. You do not need to call any wallet deployment endpoint — move directly to Source of Funds.

Step 9 — Source of Funds

Get questions

GET /v1/yativo-card/customers/{yativoCardId}/source-of-funds
Authorization: Bearer YOUR_TOKEN
Response
{
  "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

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" }
  ]
}
Response
{
  "success": true,
  "data": {
    "status": "completed",
    "next_step": "phone_verification"
  }
}

Step 10 — Phone Verification

Request SMS code

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

{
  "phoneNumber": "+14155552671"
}
Response
{
  "success": true,
  "message": "Verification code sent"
}

Verify SMS code

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

{
  "code": "382910"
}
Response
{
  "success": true,
  "data": {
    "is_verified": true,
    "next_step": "create_virtual_card"
  }
}

Step 11 — Create Virtual Card

All prerequisites are complete. Issue the card:
POST /v1/yativo-card/customers/{yativoCardId}/cards/virtual
Authorization: Bearer YOUR_TOKEN
Response 201
{
  "success": true,
  "data": {
    "card_id": "afeb85fe-02f8-48da-b61e-84ad02704167",
    "card_type": "virtual",
    "status": "active",
    "last_four": "0892",
    "activated_at": "2026-04-25T14:35:41.418Z"
  }
}
To show the customer their full card number and CVV, request a secure view URL (see Secure Card View 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.
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:
403 Limit reached
{
  "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:
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.
Response 200
{
  "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 codeMeaning
LIMIT_EXCEEDS_PROGRAMThe value you sent is above your program’s ceiling for that limit type
NO_CHANGESNo 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.
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"
}
Response 200
{
  "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 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:
GET /v1/yativo-card/customers/{yativoCardId}/wallet/funding-address
Authorization: Bearer YOUR_TOKEN
Response
{
  "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

GET /v1/card-issuer/customers?status=card_created&page=1&limit=20
Authorization: Bearer YOUR_TOKEN
Response
{
  "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:
GET /v1/card-issuer/lookup-customer?external_id=usr_8821
Authorization: Bearer YOUR_TOKEN
Supported query parameters (pass exactly one):
ParamDescription
yativo_card_idYativo’s internal card customer ID
customer_idMongoDB customer ObjectId (or yativo_card_id as fallback)
card_idCard ID from card creation
external_idYour reference ID set at onboarding
emailCustomer email address
idMongoDB _id of the customer record
Response
{
  "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

POST /v1/yativo-card/customers/{yativoCardId}/cards/{cardId}/freeze
Authorization: Bearer YOUR_TOKEN
Response
{
  "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"
  }
}
POST /v1/yativo-card/customers/{yativoCardId}/cards/{cardId}/unfreeze
Authorization: Bearer YOUR_TOKEN
Response
{
  "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

GET /v1/yativo-card/customers/{yativoCardId}/cards/{cardId}/transactions?limit=20
Authorization: Bearer YOUR_TOKEN
Response
{
  "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:
GET /v1/yativo-card/customers/{yativoCardId}/wallet/limits
Authorization: Bearer YOUR_TOKEN
Response
{
  "success": true,
  "data": {
    "currency": "USD",
    "limits": {
      "daily": {
        "limit": 7999,
        "used": 0,
        "remaining": 7999
      },
      "monthly": {},
      "per_transaction": {}
    }
  }
}
Update the daily spending limit:
PUT /v1/yativo-card/customers/{yativoCardId}/wallet/limits
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "dailyLimit": 2500
}
Response
{
  "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"
  }
}
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.

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

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
  }
}
FieldTypeDescription
enabled_viewsarrayWhich panels to show: "data" (PAN/CVV/expiry), "pin". Defaults to both.
require_access_codebooleanRequire the cardholder to enter a code before the card data unlocks.
access_codestringThe code the cardholder must enter (set by you, shared via your app).
theme.accent_colorstringHex color for buttons and highlights.
theme.background_colorstringPage background color.
theme.panel_colorstringCard panel background color.
theme.text_colorstringPrimary text color.
theme.logo_urlstringYour logo shown at the top of the page.
theme.border_radiusnumberCorner radius in px (8–36).
theme.font_familystringCSS font-family string.
Response
{
  "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.
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, Gnosis Pay fires a physical.card.pin.changed webhook and Yativo updates pin_set: true on the card record.
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.

Deposits

View all funding deposits into your issuer program (SOL auto-funding and XDC manual deposits):
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:
GET /v1/card-issuer/transfers?page=1&limit=20
Authorization: Bearer YOUR_TOKEN
Response
{
  "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.

Single transfer status

GET /v1/card-issuer/transfers/{transferId}
Authorization: Bearer YOUR_TOKEN
See Get Transfer Status for the full response shape and status enum.

Transfers for a specific customer

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.

Check Progress Anytime

GET /v1/yativo-card/customers/{yativoCardId}/progress
Authorization: Bearer YOUR_TOKEN
Response
{
  "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:
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.
POST /v1/yativo-card/customers/{yativoCardId}/reset
Authorization: Bearer YOUR_TOKEN
Response 200
{
  "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."
  }
}
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.
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:
400 Card already issued
{
  "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 for payload examples, signature verification, and the full event reference.
EventTrigger
customer.fundedFunding transfer completed into a customer’s card wallet
customer.funding.failedFunding transfer failed
master_wallet.customer_fundedMaster wallet debited for a customer funding (includes remaining_balance on direct transfers)
master_wallet.depositFunds received into your master wallet
master_wallet.swapToken swap submitted from your master wallet
card.createdNew card issued to a customer
card.activatedPhysical card activated
card.frozenCard frozen
card.unfrozenCard unfrozen
card.voidedCard permanently voided
card.lostCard reported lost
card.stolenCard reported stolen
card.cancelledCard cancelled
transaction.authorizedCard transaction authorized at point of sale
transaction.settledTransaction cleared and settled
transaction.declinedTransaction declined
transaction.reversedTransaction reversed
transaction.refund.createdRefund created
customer.balance.updatedCustomer balance changed after a spend, top-up, or authorization
KYC status changes are not delivered as webhooks — poll Get Customer or Look Up Customer to check kyc_status after directing customers through the KYC link.


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

              ▼  (auto background — no action needed)
         safe_deployed

              ▼  POST /customers/{id}/cards/virtual
          card_created


           active  (terminal — no further steps)

Handling All Error Codes

OTP Phase errors

Error codeCauseRecovery call
OTP_EXPIRED10-min window elapsedPOST /customers/{id}/resend-otp
OTP_INVALID_OR_EXPIREDWrong or expired codeRetry with correct code, or POST /customers/{id}/resend-otp
OTP_VERIFICATION_FAILEDVerification service returned an unexpected errorInspect message field; then resend-otp
OTP_ATTEMPTS_EXCEEDED5 wrong attemptsPOST /customers/{id}/resend-otpthis resets the counter
SESSION_EXPIREDJWT expired and SIWE auto-refresh failedPOST /customers/{id}/resend-otp — new OTP re-establishes the session
JWT_REFRESH_FAILEDSIWE 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 codeCauseRecovery call
KYC_LINK_FAILEDIdentity verification link could not be generatedRetry GET /customers/{id}/kyc-link?refresh=true after a short delay
KYC_STATUS_ERRORLive status check failedRetry GET /customers/{id}/kyc-status — response falls back to cached status
SESSION_EXPIREDJWT expired and can’t refreshGET /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.
POST /v1/yativo-card/customers/{yativoCardId}/resume
Authorization: Bearer YOUR_TOKEN
What it does by state:
flow_statusAction takennext_step returned
otp_requestedResends OTP to customer email, resets attempt counterverify_otp
reauth_otp_requestedSame as above (re-auth OTP pending)verify_otp
otp_verifiedGenerates a fresh KYC linkkyc_link
kyc_initiatedGenerates a fresh KYC linkkyc_link
kyc_rejectedGenerates a fresh KYC link for retryretry_kyc
kyc_completed / kyc_approved / safe_deployedNo action needed — ready for card creationcreate_virtual_card
card_created / activeAlready completenone
Response — OTP phase
{
  "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
  }
}
Response — KYC phase
{
  "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
  }
}
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.

Polling Strategy

For KYC status polling, use an exponential-backoff approach with a hard timeout:
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:
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 valueWhat to do
verify_otpShow OTP input to customer
resend_otpOTP is unusable — call resend-otp then show OTP input
kyc_linkOpen kyc_url or embed SDK
kyc_statusStart polling kyc-status
retry_kycKYC rejected — show reason, get new link, redirect customer
create_virtual_cardKYC done, Safe deployed — call POST /cards/virtual
noneCustomer is fully active, no further action
nullIntermediate state — call GET /progress for full picture

Common Integration Mistakes

Do not hardcode state transitions. Always read next_step from the response. States can be added between releases.
MistakeConsequenceFix
Using issuer email as customer emailISSUER_EMAIL_NOT_ALLOWED — blocked at onboardUse the customer’s own email address
Not storing yativo_card_idCan’t call any subsequent customer endpointStore it immediately after POST /customers/onboard
Treating OTP_ATTEMPTS_EXCEEDED as fatalCustomer stuckCall resend-otp — resets the counter
Not handling kyc_rejectedCustomer stuck in KYC loopDetect rejection in kyc-status, fetch fresh link, re-direct
Re-creating customer after OTP failureDuplicate recordsCall resend-otp on the existing yativo_card_id
Calling kyc-link for otp_requested customersSESSION_EXPIRED or wrong stepEnsure OTP is verified before requesting KYC link
Assuming Safe deploy needs manual callExtra API call, possible conflictFor customers: Safe deploys automatically after KYC approval

Quick Reference

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

ActionMethodPath
Check eligibilityGET/v1/card-issuer/eligibility
ApplyPOST/v1/card-issuer/apply
Check program statusGET/v1/card-issuer/status
Get master wallet balancesGET/v1/card-issuer/master-wallets
Swap master wallet currenciesPOST/v1/card-issuer/master-wallets/swap
List customersGET/v1/card-issuer/customers
Look up customerGET/v1/card-issuer/lookup-customer
Fund a customer cardPOST/v1/card-issuer/fund-customer
List all transfersGET/v1/card-issuer/transfers
Get transfer statusGET/v1/card-issuer/transfers/{transferId}
List customer transfersGET/v1/card-issuer/customers/{id}/transfers
Deposits listGET/v1/card-issuer/deposits
Delete incomplete customerDELETE/v1/card-issuer/customers/{id}

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

ActionMethodPath
Onboard customerPOST/v1/yativo-card/customers/onboard
Verify OTPPOST/v1/yativo-card/customers/{id}/verify-otp
Resend OTPPOST/v1/yativo-card/customers/{id}/resend-otp
Resume stuck onboardingPOST/v1/yativo-card/customers/{id}/resume
Get KYC linkGET/v1/yativo-card/customers/{id}/kyc-link
Poll KYC statusGET/v1/yativo-card/customers/{id}/kyc-status
Get SoF questionsGET/v1/yativo-card/customers/{id}/source-of-funds
Submit SoF answersPOST/v1/yativo-card/customers/{id}/source-of-funds
Request phone OTPPOST/v1/yativo-card/customers/{id}/phone/request-verification
Verify phone OTPPOST/v1/yativo-card/customers/{id}/phone/verify
Create virtual cardPOST/v1/yativo-card/customers/{id}/cards/virtual
Set per-customer card limitsPATCH/v1/yativo-card/customers/{id}/card-limits
Reset customer onboardingPOST/v1/yativo-card/customers/{id}/reset
Get deposit addressGET/v1/yativo-card/customers/{id}/wallet/funding-address
Check onboarding progressGET/v1/yativo-card/customers/{id}/progress
Freeze cardPOST/v1/yativo-card/customers/{id}/cards/{cardId}/freeze
Unfreeze cardPOST/v1/yativo-card/customers/{id}/cards/{cardId}/unfreeze
Terminate cardPOST/v1/yativo-card/customers/{id}/cards/{cardId}/void
Get transactionsGET/v1/yativo-card/customers/{id}/cards/{cardId}/transactions
Get spending limitsGET/v1/yativo-card/customers/{id}/wallet/limits
Set spending limitPUT/v1/yativo-card/customers/{id}/wallet/limits
Get secure card view URLPOST/v1/yativo-card/customers/{id}/cards/{cardId}/view-token
Get customer profileGET/v1/yativo-card/customers/{id}/profile