Issue and Manage a Card

Issue virtual and physical payment cards linked to user wallets.

Before You Start

Read the following guides before proceeding:

GuideWhy
Getting StartedPlatform overview and setup
Api BasicsRequired headers and request configuration
AuthenticationHow to obtain access tokens
OnboardingUser and wallet registration
KYCKYC verification requirements

Overview

Wirex BaaS supports issuing Visa payment cards linked to user Smart Wallets. Cards can be:

  • Virtual — Digital cards for online payments, issued instantly
  • Physical — Plastic cards delivered to the user's address

Cards are funded from the user's unified balance (WUSD/WEUR) and can be used anywhere Visa is accepted.


Prerequisites

Before issuing a card, check user eligibility via the user endpoint. Capabilities are returned as part of the user response.

GET /api/v2/user
const response = await fetch(`${baseUrl}/api/v2/user`, {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId
  }
});
const user = await response.json();

const virtualCardCapability = user.capabilities.find(c => c.type === 'VisaVirtualCard');
if (virtualCardCapability?.status === 'Active') {
  // User can issue virtual cards
}
import requests

response = requests.get(
    f"{base_url}/api/v2/user",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id
    }
)
user = response.json()

virtual_card = next((c for c in user["capabilities"] if c["type"] == "VisaVirtualCard"), None)
if virtual_card and virtual_card["status"] == "Active":
    # User can issue virtual cards
    pass
req, _ := http.NewRequest("GET", baseURL+"/api/v2/user", nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var user UserResponse
json.NewDecoder(resp.Body).Decode(&user)

for _, cap := range user.Capabilities {
    if cap.Type == "VisaVirtualCard" && cap.Status == "Active" {
        // User can issue virtual cards
    }
}

Response (relevant fields):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "owner": "0x1234567890abcdef1234567890abcdef12345678",
  "verification": {
    "status": "Approved",
    "levels": [
      {
        "status": "Approved",
        "type": "SDD",
        "name": "Wirex SDD Level"
      }
    ]
  },
  "capabilities": [
    {
      "type": "VisaVirtualCard",
      "verification_requirements": [
        { "type": "BDD", "order": 1 }
      ],
      "prerequisites": null,
      "status": "Active"
    },
    {
      "type": "VisaPlasticCard",
      "verification_requirements": [
        { "type": "SDD", "order": 1 }
      ],
      "prerequisites": null,
      "status": "Active"
    }
  ]
}

Card Capabilities

CapabilityDescription
VisaVirtualCardIssue virtual Visa cards
VisaPlasticCardIssue physical Visa cards

Capability Fields

FieldDescription
typeCapability identifier
statusCurrent status (see below)
status_reasonExplanation when status is not Active
verification_requirementsKYC levels required for this capability
prerequisitesOther capabilities that must be active first

Capability Status

StatusDescriptionAction
ActiveReady to issue cardsProceed with issuance
NotFulfilledRequirements not metCheck status_reason for details
NotAvailableNot available for user/regionCannot issue this card type

Example: NotFulfilled Due to Verification Level

{
  "type": "VisaPlasticCard",
  "verification_requirements": [
    { "type": "SDD", "order": 1 }
  ],
  "prerequisites": null,
  "status": "NotFulfilled",
  "status_reason": "Verification level SDD is required"
}

The user has completed BDD verification but VisaPlasticCard requires SDD level. The user must complete SDD verification before ordering a plastic card


Order Fees & Delivery

These lookups are shared by both issuance flows. Use them to check fees and present delivery options before issuing; the issue calls are in Issuing a Card below.

Check Order Fees

Returns the order fee for a given card type in a given country.

GET /api/v1/cards/{type}/fees/{country}

Path parameters (values that go directly into the URL):

ParameterDescription
{type}Card type to price: virtual, plastic, or metal (case-insensitive)
{country}Cardholder's country as an ISO 3166-1 alpha-2 code, e.g. GB

Response:

{
  "currency": "USD",
  "order_fee": 9.99,
  "estimated_payment_amounts": [
    {
      "amount": 10.05,
      "precise_amount": "10050000",
      "token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "token_symbol": "USDC",
      "rate": 0.9940
    }
  ]
}
FieldDescription
order_feeFee amount in fiat currency
estimated_payment_amountsFee converted to supported tokens

If the endpoint returns a 400 error with "Card fees are not enforced", fees are not applicable for your company — skip the fee steps and issue the card directly (v1 Step 4 / v2 Step 3).

const feesResponse = await fetch(`${baseUrl}/api/v1/cards/virtual/fees/${country}`, {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId
  }
});

if (feesResponse.status === 400) {
  // Fees not enforced, skip to issuing card
} else {
  const fees = await feesResponse.json();
  // Proceed to create fees invoice
}
response = requests.get(
    f"{base_url}/api/v1/cards/virtual/fees/{country}",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id
    }
)

if response.status_code == 400:
    # Fees not enforced, skip to issuing card
    pass
else:
    fees = response.json()
    # Proceed to create fees invoice
req, _ := http.NewRequest("GET", baseURL+"/api/v1/cards/virtual/fees/"+country, nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

if resp.StatusCode == 400 {
    // Fees not enforced, skip to issuing card
} else {
    var fees FeesResponse
    json.NewDecoder(resp.Body).Decode(&fees)
    // Proceed to create fees invoice
}

Delivery Countries

GET /api/v1/cards/delivery/countries

Response:

["GB", "DE", "FR", "ES", "IT"]
const countriesResponse = await fetch(`${baseUrl}/api/v1/cards/delivery/countries`, {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId
  }
});
const countries = await countriesResponse.json();
response = requests.get(
    f"{base_url}/api/v1/cards/delivery/countries",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id
    }
)
countries = response.json()
req, _ := http.NewRequest("GET", baseURL+"/api/v1/cards/delivery/countries", nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var countries []string
json.NewDecoder(resp.Body).Decode(&countries)

Delivery Methods

GET /api/v1/cards/delivery/methods/{country}

Path parameters (values that go directly into the URL):

ParameterDescription
{country}Delivery country as an ISO 3166-1 alpha-2 code, e.g. GB

Response:

[
  {
    "fee": 15.00,
    "currency": "USD",
    "provider": "DHL",
    "estimated_payment_amounts": [
      {
        "amount": 15.10,
        "precise_amount": "15100000",
        "token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "token_symbol": "USDC",
        "rate": 0.9934
      }
    ]
  }
]
FieldDescription
feeDelivery fee in fiat currency
currencyFiat currency for the fee
providerCourier provider (pass when issuing the card)
estimated_payment_amountsFee converted to supported tokens

Display available methods to the user and use the selected provider value when issuing the card.

const methodsResponse = await fetch(`${baseUrl}/api/v1/cards/delivery/methods/${country}`, {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId
  }
});
const methods = await methodsResponse.json();
const selectedProvider = methods[0].provider; // e.g., 'DHL'
response = requests.get(
    f"{base_url}/api/v1/cards/delivery/methods/{country}",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id
    }
)
methods = response.json()
selected_provider = methods[0]["provider"]  # e.g., 'DHL'
req, _ := http.NewRequest("GET", baseURL+"/api/v1/cards/delivery/methods/"+country, nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var methods []DeliveryMethod
json.NewDecoder(resp.Body).Decode(&methods)
selectedProvider := methods[0].Provider // e.g., "DHL"

Delivery Address Fields

The same delivery_address object and validation apply to all physical cards (Plastic and Metal) on both v1 and v2 endpoints.

FieldRequiredValidation (regex)Notes
line1Yes^[A-Za-z0-9&.,'’\-/() :+#]{2,100}$Street address
line2No^[A-Za-z0-9&.,'’\-/() :+#]{2,100}$Apartment, suite, etc.
cityYes^[A-Za-z\s\-'.]{2,100}$Letters only — no digits
stateNo^[A-Za-z][A-Za-z\s\-']{1,99}$State or province
zip_codeYes^[A-Za-z0-9\s\-]{3,12}$3–12 chars; always required
countryYes^[A-Z]{2}$ISO 3166-1 alpha-2, uppercase (e.g. GB)

Notes:

  • zip_code is always required, even for countries that have no postal code. Send a placeholder that matches the regex (3+ chars), e.g. "00000". Only the format is validated, not real postal validity.
  • A missing required field returns 400 ErrorMissingField; a bad format returns 400 ErrorInvalidField. The offending field is named in error_details (e.g. delivery_address.zip_code).
  • These are the platform's format checks, applied before the order is sent to the card processor. If they pass but the processor rejects the order (e.g. address or limit), the issue call returns a synchronous 400 "Request failed due to external validation failure" — no card is created and no card webhook is sent.

Response:

{
  "id": "64120850-73a1-4df5-a074-d463258c9deb"
}
const issueResponse = await fetch(`${baseUrl}/api/v2/cards/plastic`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    delivery_id: deliveryId,
    delivery_provider: 'DHL',
    card_name: 'My Travel Card',
    name_on_card: 'Alex Grey',
    delivery_address: {
      line1: '10 Downing Street',
      line2: 'Flat 2',
      city: 'London',
      state: '',
      zip_code: 'SW1A 2AA',
      country: 'GB'
    }
  })
});
const card = await issueResponse.json();
response = requests.post(
    f"{base_url}/api/v2/cards/plastic",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id,
        "Content-Type": "application/json"
    },
    json={
        "delivery_id": delivery_id,
        "delivery_provider": "DHL",
        "card_name": "My Travel Card",
        "name_on_card": "Alex Grey",
        "delivery_address": {
            "line1": "10 Downing Street",
            "line2": "Flat 2",
            "city": "London",
            "state": "",
            "zip_code": "SW1A 2AA",
            "country": "GB"
        }
    }
)
card = response.json()
body, _ := json.Marshal(map[string]interface{}{
    "delivery_id":       deliveryId,
    "delivery_provider": "DHL",
    "card_name":         "My Travel Card",
    "name_on_card":      "Alex Grey",
    "delivery_address": map[string]string{
        "line1":    "10 Downing Street",
        "line2":    "Flat 2",
        "city":     "London",
        "state":    "",
        "zip_code": "SW1A 2AA",
        "country":  "GB",
    },
})
req, _ := http.NewRequest("POST", baseURL+"/api/v2/cards/plastic", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var card CardResponse
json.NewDecoder(resp.Body).Decode(&card)

Issuing a Card

Issuing a card always carries an issuance fee, paid in crypto. Both API versions issue the same cards with the same delivery options — they differ only in how that fee is collected:

  • v1 — External Fee — you settle the issuance fee yourself. Wirex returns an on-chain invoice (a recipient_address and an amount); you send the payment from a wallet you control and pass the transaction hash back to Wirex when issuing the card. Because you cover the fee directly instead of relying on the cardholder's balance, v1 is the universal option: it fits standard on-chain integrations, and it is the only way to issue cards under External Authorization, where funds are held on your Master Account rather than in per-user on-chain balances.
  • v2 — Direct Fee — Wirex debits the fee automatically from the cardholder's own on-chain balance at the moment of issuance, so you never move funds yourself. Because it draws on that balance, v2 is limited to standard on-chain integrations and cannot be used under External Authorization, where cardholders have no on-chain balance to debit.

In both flows the fee is paid from crypto — the difference is only whose balance it comes from and who initiates the transfer. Pick one flow and follow its steps end to end.

v1 — External Feev2 — Direct Fee
How the fee is collectedYou transfer it on-chain yourselfAuto-debited from the cardholder's on-chain balance
Funded fromA wallet you controlThe cardholder's on-chain balance
Fee invoice returnsrecipient_address (where to send the fee)delivery_id (reference for issuance)
Field passed at issuancepayment_transaction_hashdelivery_id
Works inOn-chain and External Authorization (required for External Authorization)On-chain flow only (not External Authorization)

Issuance endpoints are /api/v1/cards/plastic and /api/v1/cards/virtual (and the /api/v2/ equivalents). Physical (plastic) cards require a delivery_address; virtual cards do not — see Delivery Address Fields.

v1 — External Fee

Step 1: Check order fees & delivery options

Look up the fee with Check Order Fees, and for physical cards the supported Delivery Countries and Delivery Methods. If fees are not enforced for your company, skip Steps 2–3 and issue the card with no fee field.

Step 2: Create the fee invoice

POST /api/v1/cards/{type}/fees/{country}/payment

Path parameters (values that go directly into the URL):

ParameterDescription
{type}Card type being issued: virtual, plastic, or metal (case-insensitive)
{country}Cardholder's country as an ISO 3166-1 alpha-2 code, e.g. GB

Request body:

{ 
  "token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 
  "delivery_provider": "DHL" 
}
FieldRequiredDescription
token_addressYesToken used to pay the fee
delivery_providerConditionalRequired for physical cards

Response — returns a recipient_address and a payment_amount, but no delivery_id (in v1 the fee is not auto-debited; you pay it yourself in Step 3):

{
  "recipient_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "payment_amount": { 
    "amount": 10.05, 
    "precise_amount": "10050000", 
    "token_address": "0xA0b8...", 
    "token_symbol": "USDC", 
    "rate": 0.9940 
  }
}

Step 3: Pay the fee on-chain

Transfer payment_amount.precise_amount of payment_amount.token_address to recipient_address from a wallet you control, and keep the transaction hash.

Step 4: Issue the card

POST /api/v1/cards/{type}

Path parameters (values that go directly into the URL):

ParameterDescription
{type}Card type to issue: virtual or plastic (lowercase). Virtual cards are issued instantly and take no delivery_address; plastic cards are physical and require a delivery_address
{
  "payment_transaction_hash": "0x1234...cdef",
  "delivery_provider": "DHL",
  "name_on_card": "Alex Grey",
  "delivery_address": { 
    "line1": "10 Downing Street", 
    "city": "London", 
    "zip_code": "SW1A 2AA", 
    "country": "GB"
  }
}
FieldTypeRequiredDescription
payment_transaction_hashstringConditionalHash of the Step 3 transfer. Required when fees are enforced; omit otherwise
delivery_addressobjectYes (physical)Omit for virtual cards. See Delivery Address Fields
delivery_providerstringNoCourier provider
card_namestringNoDisplay name (1–50 chars)
name_on_cardstringNoName printed on card (2–26 chars)
const res = await fetch(`${baseUrl}/api/v1/cards/plastic`, {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${accessToken}`, 'X-User-Address': userEoaAddress, 'X-Chain-Id': chainId, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    payment_transaction_hash: paymentTxHash, // omit if fees not enforced
    name_on_card: 'Alex Grey',
    delivery_address: { line1: '10 Downing Street', city: 'London', zip_code: 'SW1A 2AA', country: 'GB' }
  })
});
const card = await res.json(); // { id }
res = requests.post(
    f"{base_url}/api/v1/cards/plastic",
    headers={"Authorization": f"Bearer {access_token}", "X-User-Address": user_eoa_address, "X-Chain-Id": chain_id, "Content-Type": "application/json"},
    json={
        "payment_transaction_hash": payment_tx_hash,  # omit if fees not enforced
        "name_on_card": "Alex Grey",
        "delivery_address": {"line1": "10 Downing Street", "city": "London", "zip_code": "SW1A 2AA", "country": "GB"},
    },
)
card = res.json()  # { id }
body, _ := json.Marshal(map[string]interface{}{
    "payment_transaction_hash": paymentTxHash, // omit if fees not enforced
    "name_on_card":             "Alex Grey",
    "delivery_address":         map[string]string{"line1": "10 Downing Street", "city": "London", "zip_code": "SW1A 2AA", "country": "GB"},
})
req, _ := http.NewRequest("POST", baseURL+"/api/v1/cards/plastic", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var card CardResponse
json.NewDecoder(resp.Body).Decode(&card)

v2 — Direct Fee

Step 1: Check order fees & delivery options

Look up the fee with Check Order Fees, and for physical cards the supported Delivery Countries and Delivery Methods. If fees are not enforced for your company, skip Step 2 and issue the card with no fee field.

Step 2: Create the fee invoice

POST /api/v2/cards/{type}/fees/{country}/payment

Path parameters (values that go directly into the URL):

ParameterDescription
{type}Card type being issued: virtual, plastic, or metal (case-insensitive)
{country}Cardholder's country as an ISO 3166-1 alpha-2 code, e.g. GB

Request body:

{ 
  "token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 
  "delivery_provider": "DHL" 
}
FieldRequiredDescription
token_addressYesToken used to pay the fee
delivery_providerConditionalRequired for physical cards

Response — the delivery_id is passed to the issue call; the fee is debited automatically at issuance:

{
  "delivery_id": 12345,
  "recipient_address": "0x...",
  "payment_amount": { "amount": 10.05, "precise_amount": "10050000", "token_address": "0xA0b8...", "token_symbol": "USDC", "rate": 0.9940 }
}

Step 3: Issue the card

POST /api/v2/cards/{type}

Path parameters (values that go directly into the URL):

ParameterDescription
{type}Card type to issue: virtual or plastic (lowercase). Virtual cards are issued instantly and take no delivery_address; plastic cards are physical and require a delivery_address
{
  "delivery_id": 12345,
  "delivery_provider": "DHL",
  "name_on_card": "Alex Grey",
  "delivery_address": { "line1": "10 Downing Street", "city": "London", "zip_code": "SW1A 2AA", "country": "GB" }
}
FieldTypeRequiredDescription
delivery_idintegerConditionalFrom Step 2. Required when fees are enforced; omit otherwise
delivery_addressobjectYes (physical)Omit for virtual cards. See Delivery Address Fields
delivery_providerstringNoCourier provider
card_namestringNoDisplay name (1–50 chars)
name_on_cardstringNoName printed on card (2–26 chars)
const res = await fetch(`${baseUrl}/api/v2/cards/plastic`, {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${accessToken}`, 'X-User-Address': userEoaAddress, 'X-Chain-Id': chainId, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    delivery_id: deliveryId, // omit if fees not enforced
    name_on_card: 'Alex Grey',
    delivery_address: { line1: '10 Downing Street', city: 'London', zip_code: 'SW1A 2AA', country: 'GB' }
  })
});
const card = await res.json(); // { id }
res = requests.post(
    f"{base_url}/api/v2/cards/plastic",
    headers={"Authorization": f"Bearer {access_token}", "X-User-Address": user_eoa_address, "X-Chain-Id": chain_id, "Content-Type": "application/json"},
    json={
        "delivery_id": delivery_id,  # omit if fees not enforced
        "name_on_card": "Alex Grey",
        "delivery_address": {"line1": "10 Downing Street", "city": "London", "zip_code": "SW1A 2AA", "country": "GB"},
    },
)
card = res.json()  # { id }
body, _ := json.Marshal(map[string]interface{}{
    "delivery_id":      deliveryId, // omit if fees not enforced
    "name_on_card":     "Alex Grey",
    "delivery_address": map[string]string{"line1": "10 Downing Street", "city": "London", "zip_code": "SW1A 2AA", "country": "GB"},
})
req, _ := http.NewRequest("POST", baseURL+"/api/v2/cards/plastic", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var card CardResponse
json.NewDecoder(resp.Body).Decode(&card)

Pass delivery_id (there is no invoice_id field). If fees are enforced and delivery_id is missing or 0, issuance returns 400 "Delivery ID is required and must be positive".

Both versions return { "id": "..." }; the card starts in Requested and transitions to NotActivated once processed. Capabilities, order fees, delivery options, address validation, lifecycle, activation, and webhooks in this guide apply to both versions.


Card Lifecycle

Requested → NotActivated → Active
                             ↓
                          Blocked ←→ Active
                             ↓
                           Closed
StatusDescription
RequestedCard order submitted, processing
NotActivatedCard issued, awaiting activation
ActiveCard is active and usable
BlockedTemporarily blocked (can be unblocked)
ClosedPermanently closed

Activate Card

Activation is required only for physical cards. This protects user funds if the card is stolen during shipment.

PUT /api/v1/cards/{cardId}/activate

Path parameters (values that go directly into the URL):

ParameterDescription
{cardId}The card's id, returned when the card was issued (see Get Cards)

Request:

{
  "card_number_last4": "1234",
  "expiry_date": "12/2027"
}
FieldTypeRequiredDescription
card_number_last4stringYesLast 4 digits of card number
expiry_datestringYesExpiration date in MM/YYYY format

Response: Empty on success (200 OK)

await fetch(`${baseUrl}/api/v1/cards/${cardId}/activate`, {
  method: 'PUT',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    card_number_last4: '1234',
    expiry_date: '12/2027'
  })
});
requests.put(
    f"{base_url}/api/v1/cards/{card_id}/activate",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id,
        "Content-Type": "application/json"
    },
    json={
        "card_number_last4": "1234",
        "expiry_date": "12/2027"
    }
)
body, _ := json.Marshal(map[string]string{
    "card_number_last4": "1234",
    "expiry_date":       "12/2027",
})
req, _ := http.NewRequest("PUT", baseURL+"/api/v1/cards/"+cardId+"/activate", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")

http.DefaultClient.Do(req)

Get Cards

Retrieve all cards for a user.

GET /api/v1/cards
const response = await fetch(`${baseUrl}/api/v1/cards`, {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId
  }
});
const { data: cards } = await response.json();
response = requests.get(
    f"{base_url}/api/v1/cards",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id
    }
)
cards = response.json()["data"]
req, _ := http.NewRequest("GET", baseURL+"/api/v1/cards", nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var result CardsResponse
json.NewDecoder(resp.Body).Decode(&result)
cards := result.Data

Response:

{
  "data": [
    {
      "id": "64120850-73a1-4df5-a074-d463258c9deb",
      "card_wallet_address": "0xAAFF0821A09A1Aac28B72dD3Ff410A7ea5FEb874",
      "status": "Active",
      "status_reason": "",
      "previous_status": "NotActivated",
      "generation": "Gen2",
      "provider": "Wirex",
      "limit": {
        "daily_limit": 10000.00,
        "daily_usage": 250.00,
        "currency": "USD"
      },
      "allowed_actions": [
        { "type": "Block", "relative_path": "/api/v1/cards/:cardId/block" },
        { "type": "Close", "relative_path": "/api/v1/cards/:cardId/close" },
        { "type": "SetLimit", "relative_path": "/api/v1/cards/:cardId/limit" },
        { "type": "GetDetails", "relative_path": "/api/v1/cards/:cardId/details" },
        { "type": "GetCvv", "relative_path": "/api/v1/cards/:cardId/cvv" }
      ],
      "card_data": {
        "card_name": "My Card",
        "name_on_card": "Alex Grey",
        "card_number_last_4": "0333",
        "expiry_date": "12/2027",
        "payment_system": "Visa",
        "format": "Virtual"
      },
      "created_at": "2024-01-01T10:00:00Z",
      "updated_at": "2024-01-01T10:05:00Z"
    }
  ]
}

Allowed Action Types

TypeDescription
ActivateActivate the card
BlockBlock the card
UnblockUnblock the card
CloseClose the card
SetLimitChange card limits
GetDetailsGet full card details (PAN)
GetCvvGet card CVV
GetPinGet card PIN

Card Data Visibility

FieldVisible When
card_number_first_4Status is Requested or NotActivated
card_number_last_4Status is Active, Blocked, or Closed
expiry_dateStatus is Active, Blocked, or Closed

Manage Cards

In every call below, {cardId} in the URL is the card's id returned at issuance (see Get Cards).

Block Card

Temporarily block a card. Can be unblocked later.

PUT /api/v1/cards/{cardId}/block

Unblock Card

Restore a blocked card to active status.

PUT /api/v1/cards/{cardId}/unblock

Close Card

Permanently close a card. This action cannot be undone.

PUT /api/v1/cards/{cardId}/close
// Block
await fetch(`${baseUrl}/api/v1/cards/${cardId}/block`, {
  method: 'PUT',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId
  }
});

// Unblock
await fetch(`${baseUrl}/api/v1/cards/${cardId}/unblock`, {
  method: 'PUT',
  headers: { /* same headers */ }
});

// Close
await fetch(`${baseUrl}/api/v1/cards/${cardId}/close`, {
  method: 'PUT',
  headers: { /* same headers */ }
});
headers = {
    "Authorization": f"Bearer {access_token}",
    "X-User-Address": user_eoa_address,
    "X-Chain-Id": chain_id
}

# Block
requests.put(f"{base_url}/api/v1/cards/{card_id}/block", headers=headers)

# Unblock
requests.put(f"{base_url}/api/v1/cards/{card_id}/unblock", headers=headers)

# Close
requests.put(f"{base_url}/api/v1/cards/{card_id}/close", headers=headers)
actions := []string{"block", "unblock", "close"}
for _, action := range actions {
    req, _ := http.NewRequest("PUT", baseURL+"/api/v1/cards/"+cardId+"/"+action, nil)
    req.Header.Set("Authorization", "Bearer "+accessToken)
    req.Header.Set("X-User-Address", userEoaAddress)
    req.Header.Set("X-Chain-Id", chainId)
    http.DefaultClient.Do(req)
}

Webhooks

Card status changes trigger webhook notifications. The payload structure matches the Get Cards response.

Card Status Webhook

Endpoint: POST {your_webhook_base_url}/v2/webhooks/cards

The payload structure matches the Get Cards response item.

{
  "id": "64120850-73a1-4df5-a074-d463258c9deb",
  "card_wallet_address": "0xAAFF0821A09A1Aac28B72dD3Ff410A7ea5FEb874",
  "status": "Active",
  "status_reason": "",
  "previous_status": "NotActivated",
  "generation": "Gen2",
  "provider": "Wirex",
  "limit": {
    "daily_limit": 10000.00,
    "daily_usage": 0.00,
    "currency": "USD"
  },
  "allowed_actions": [
    { "type": "Block", "relative_path": "/api/v1/cards/:cardId/block" },
    { "type": "Close", "relative_path": "/api/v1/cards/:cardId/close" }
  ],
  "card_data": {
    "card_name": "My Card",
    "name_on_card": "Alex Grey",
    "card_number_last_4": "0333",
    "expiry_date": "12/2027",
    "payment_system": "Visa",
    "format": "Virtual"
  },
  "created_at": "2024-01-01T10:00:00Z",
  "updated_at": "2024-01-01T10:05:00Z"
}

Card Limit Webhook

Endpoint: POST {your_webhook_base_url}/v2/webhooks/card-limits

{
  "card_id": "64120850-73a1-4df5-a074-d463258c9deb",
  "transaction_limit": 500.00,
  "daily_limit": 1000.00,
  "daily_usage": 200.00,
  "monthly_limit": 5000.00,
  "monthly_usage": 1500.00,
  "lifetime_limit": 100000.00,
  "lifetime_usage": 25000.00
}

Field Validation

Card Name

Pattern: ^[a-zA-Z0-9\-':+# @]{1,50}$

Name on Card

Pattern: ^[A-Za-z][A-Za-z .'-]{1,25}$

  • Must start with a letter
  • Only letters, spaces, periods, apostrophes, and hyphens
  • 2-26 characters total

Delivery Address

FieldPattern
line1^[A-Za-z0-9&.,''\-/() :+#]{2,100}$
line2^[A-Za-z0-9&.,''\-/() :+#]{2,100}$
city^[A-Za-z\s\-'.]{2,100}$
state^[A-Za-z][A-Za-z\s\-']{1,99}$
zip_code^[A-Za-z0-9\s\-]{3,12}$
country^[A-Z]{2}$ (ISO 3166-1 alpha-2)

Error Handling

All errors return JSON with the following structure:

{
  "error_reason": "ErrorInvalidField",
  "error_description": "Human-readable message",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "field", "details": "field_name" }
  ]
}

Validation Errors (400)

Missing Required Field

{
  "error_reason": "ErrorMissingField",
  "error_description": "delivery_address is required",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "field", "details": "delivery_address" },
    { "key": "issue", "details": "missing" }
  ]
}

Invalid Field Format

{
  "error_reason": "ErrorInvalidField",
  "error_description": "Invalid format for card number last4",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "field", "details": "card_number_last4" },
    { "key": "issue", "details": "invalid_format" },
    { "key": "pattern", "details": "^\\d{4}$" }
  ]
}

Invalid Card Status

Returned when attempting an action not allowed for the card's current status.

{
  "error_reason": "ErrorInvalidStatus",
  "error_description": "Card is not in valid status for this action",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "field", "details": "card_status" },
    { "key": "value", "details": "Requested" }
  ]
}
ActionInvalid Statuses
ActivateRequested, Active, Blocked, Closed
BlockNotActivated, Requested, Closed, Blocked
UnblockAny status except Blocked
CloseNotActivated, Requested, Closed

Delivery ID Required

When fees are enforced and delivery_id is missing or invalid.

{
  "error_reason": "ErrorGeneral",
  "error_description": "Delivery ID is required and must be positive",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "field", "details": "delivery_id" }
  ]
}

Capability Not Active

{
  "error_reason": "ErrorGeneral",
  "error_description": "Virtual card capability is not active",
  "error_category": {
    "category": "CategoryInternalFailure",
    "http_status_code": 500
  }
}

Not Found Errors (400)

Delivery Countries/Methods Not Found

{
  "error_reason": "ErrorNotFound",
  "error_description": "No delivery methods found for country",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "field", "details": "country" },
    { "key": "value", "details": "XX" }
  ]
}

Fees Not Found

{
  "error_reason": "ErrorNotFound",
  "error_description": "No fees found for specified country and method",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "fields", "details": "type,country" },
    { "key": "type", "details": "Virtual" },
    { "key": "country", "details": "XX" }
  ]
}

Fees Not Enforced

Returned when calling fees endpoints but fees are not configured for your company.

{
  "error_reason": "ErrorGeneral",
  "error_description": "Request validation failed",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "request", "details": "Card fees are not enforced" }
  ]
}

Server Errors (500)

Error DescriptionCause
Failed to get cardCard not found or service unavailable
Failed to issue virtual cardCard issuer service error
Failed to issue plastic cardCard issuer service error
Failed to activate cardCard issuer service error
Failed to process delivery paymentPayment processing error