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

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": {
    "bridge_id": "bridge_01HX9KZM",
    "status": "initiated",
    "source": {
      "chain": "SOL",
      "amount": 101.80,
      "wallet": "5wQsqNwqu5yTXb7aDG4eLdGYLo1pXXArMTEoGb4KPDoM"
    },
    "destination": {
      "token": "EUR"
    },
    "pricing_mode": "receive_x",
    "estimated_output": "100.00",
    "estimated_time": "2-10 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",
    "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 (60 seconds by default). Request a new one each time the customer wants to view their card.

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.

Funding History

View all card funding transactions you have initiated for customers:
GET /v1/card-issuer/funding-history?page=1&limit=20
Authorization: Bearer YOUR_TOKEN
Response
{
  "success": true,
  "data": {
    "transactions": [
      {
        "id": "6627f3a2...",
        "bridge_id": "bridge_01HX9KZM",
        "source_chain": "SOL",
        "source_amount": 101.80,
        "destination_token": "EUR",
        "destination_amount": 100.00,
        "status": "completed",
        "funding_type": "master_wallet_fund",
        "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
  }
}

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.

Webhook Events

Subscribe to webhooks for real-time events. See Webhook Integration.
EventTrigger
customer.kyc.approvedKYC passed — wallet is being provisioned
customer.kyc.rejectedKYC failed — redirect customer to retry
customer.card.createdCard issued successfully
card.transaction.approvedPurchase approved
card.transaction.declinedPurchase declined
card.transaction.completedSettlement complete
card.fundedFunding transfer completed
card.funding.failedFunding transfer failed


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
Funding historyGET/v1/card-issuer/funding-history
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
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