Issue and Manage a Card
Issue virtual and physical payment cards linked to user wallets.
Before You Start
Read the following guides before proceeding:
| Guide | Why |
|---|---|
| Getting Started | Platform overview and setup |
| Api Basics | Required headers and request configuration |
| Authentication | How to obtain access tokens |
| Onboarding | User and wallet registration |
| KYC | KYC 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
passreq, _ := 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
| Capability | Description |
|---|---|
VisaVirtualCard | Issue virtual Visa cards |
VisaPlasticCard | Issue physical Visa cards |
Capability Fields
| Field | Description |
|---|---|
type | Capability identifier |
status | Current status (see below) |
status_reason | Explanation when status is not Active |
verification_requirements | KYC levels required for this capability |
prerequisites | Other capabilities that must be active first |
Capability Status
| Status | Description | Action |
|---|---|---|
Active | Ready to issue cards | Proceed with issuance |
NotFulfilled | Requirements not met | Check status_reason for details |
NotAvailable | Not available for user/region | Cannot 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):
| Parameter | Description |
|---|---|
{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
}
]
}| Field | Description |
|---|---|
order_fee | Fee amount in fiat currency |
estimated_payment_amounts | Fee 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 invoicereq, _ := 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):
| Parameter | Description |
|---|---|
{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
}
]
}
]| Field | Description |
|---|---|
fee | Delivery fee in fiat currency |
currency | Fiat currency for the fee |
provider | Courier provider (pass when issuing the card) |
estimated_payment_amounts | Fee 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.
| Field | Required | Validation (regex) | Notes |
|---|---|---|---|
line1 | Yes | ^[A-Za-z0-9&.,'’\-/() :+#]{2,100}$ | Street address |
line2 | No | ^[A-Za-z0-9&.,'’\-/() :+#]{2,100}$ | Apartment, suite, etc. |
city | Yes | ^[A-Za-z\s\-'.]{2,100}$ | Letters only — no digits |
state | No | ^[A-Za-z][A-Za-z\s\-']{1,99}$ | State or province |
zip_code | Yes | ^[A-Za-z0-9\s\-]{3,12}$ | 3–12 chars; always required |
country | Yes | ^[A-Z]{2}$ | ISO 3166-1 alpha-2, uppercase (e.g. GB) |
Notes:
zip_codeis 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 returns400 ErrorInvalidField. The offending field is named inerror_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_addressand 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 Fee | v2 — Direct Fee | |
|---|---|---|
| How the fee is collected | You transfer it on-chain yourself | Auto-debited from the cardholder's on-chain balance |
| Funded from | A wallet you control | The cardholder's on-chain balance |
| Fee invoice returns | recipient_address (where to send the fee) | delivery_id (reference for issuance) |
| Field passed at issuance | payment_transaction_hash | delivery_id |
| Works in | On-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):
| Parameter | Description |
|---|---|
{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"
}| Field | Required | Description |
|---|---|---|
token_address | Yes | Token used to pay the fee |
delivery_provider | Conditional | Required 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):
| Parameter | Description |
|---|---|
{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"
}
}| Field | Type | Required | Description |
|---|---|---|---|
payment_transaction_hash | string | Conditional | Hash of the Step 3 transfer. Required when fees are enforced; omit otherwise |
delivery_address | object | Yes (physical) | Omit for virtual cards. See Delivery Address Fields |
delivery_provider | string | No | Courier provider |
card_name | string | No | Display name (1–50 chars) |
name_on_card | string | No | Name 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):
| Parameter | Description |
|---|---|
{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"
}| Field | Required | Description |
|---|---|---|
token_address | Yes | Token used to pay the fee |
delivery_provider | Conditional | Required 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):
| Parameter | Description |
|---|---|
{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" }
}| Field | Type | Required | Description |
|---|---|---|---|
delivery_id | integer | Conditional | From Step 2. Required when fees are enforced; omit otherwise |
delivery_address | object | Yes (physical) | Omit for virtual cards. See Delivery Address Fields |
delivery_provider | string | No | Courier provider |
card_name | string | No | Display name (1–50 chars) |
name_on_card | string | No | Name 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 noinvoice_idfield). If fees are enforced anddelivery_idis missing or0, issuance returns400 "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
| Status | Description |
|---|---|
Requested | Card order submitted, processing |
NotActivated | Card issued, awaiting activation |
Active | Card is active and usable |
Blocked | Temporarily blocked (can be unblocked) |
Closed | Permanently 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):
| Parameter | Description |
|---|---|
{cardId} | The card's id, returned when the card was issued (see Get Cards) |
Request:
{
"card_number_last4": "1234",
"expiry_date": "12/2027"
}| Field | Type | Required | Description |
|---|---|---|---|
card_number_last4 | string | Yes | Last 4 digits of card number |
expiry_date | string | Yes | Expiration 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.DataResponse:
{
"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
| Type | Description |
|---|---|
Activate | Activate the card |
Block | Block the card |
Unblock | Unblock the card |
Close | Close the card |
SetLimit | Change card limits |
GetDetails | Get full card details (PAN) |
GetCvv | Get card CVV |
GetPin | Get card PIN |
Card Data Visibility
| Field | Visible When |
|---|---|
card_number_first_4 | Status is Requested or NotActivated |
card_number_last_4 | Status is Active, Blocked, or Closed |
expiry_date | Status 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
| Field | Pattern |
|---|---|
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" }
]
}| Action | Invalid Statuses |
|---|---|
| Activate | Requested, Active, Blocked, Closed |
| Block | NotActivated, Requested, Closed, Blocked |
| Unblock | Any status except Blocked |
| Close | NotActivated, 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 Description | Cause |
|---|---|
| Failed to get card | Card not found or service unavailable |
| Failed to issue virtual card | Card issuer service error |
| Failed to issue plastic card | Card issuer service error |
| Failed to activate card | Card issuer service error |
| Failed to process delivery payment | Payment processing error |

