Issuing a Card

Order virtual and plastic corporate cards, settle the issuance fee, and link the card to an employee.

Before You Start

Read the following guides before proceeding:

GuideWhy
Getting StartedPlatform overview and setup
Api BasicsRequired headers and request configuration
AuthenticationHow to obtain the corporation token
CapabilitiesCard issuance is capability-gated
EmployeesA card is linked to an employee record

Overview

A corporate card is issued against the corporation wallet and optionally linked to an employee. Two
formats are available — Virtual and Plastic. The Corporate API does not issue metal cards.

Whether an issuance fee applies depends on your company configuration. When fees are enforced, the fee
must be settled before the card is created, and there are two flows for doing that.


Prerequisites

Read GET /api/v1/corporations and confirm the capability for the format you want is Active:

FormatCapabilityCeiling
VirtualVisaVirtualCard30 virtual cards per corporation
PlasticVisaPlasticCard15 plastic cards per corporation

Both also require the corporation profile to be Active. At the ceiling the capability flips to
NotAvailable with You have reached the maximum number of virtual cards allowed — close a card before
issuing another.


Choosing a Fee Flow

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

Call this first. Its answer decides the whole flow.

ResponseMeaningWhat to do
400 ErrorNotSupportedYou have no card order/delivery fees to payFees are not enforced for your companySkip every fee step. Issue the card directly
400 ErrorNotSupportedCard issuance is not supported for this companyNo card configuration exists for your companyContact Wirex. No issuance flow will work
200 with order_feeFees are enforcedPick v1 or v2 below

When fees are enforced, two flows reach the same card:

v1 — External feev2 — Direct fee
How the fee is collectedYou transfer it on-chain yourselfDebited by Wirex at issuance
Fee invoice endpointPOST /api/v1/cards/{type}/fees/{country}/paymentPOST /api/v2/cards/{type}/fees/{country}/payment
Fee invoice returnsrecipient_address — where to send the feedelivery_id — plus recipient_address
Field passed at issuancepayment_transaction_hashdelivery_id
Issuance endpointPOST /api/v1/cards/virtual, POST /api/v1/cards/plasticPOST /api/v2/cards/virtual, POST /api/v2/cards/plastic
Failure modeYou control the transfer; a wrong amount is your reconciliation problemThe debit is atomic with issuance

Pick one and follow it end to end. Do not mix a v2 invoice with a v1 issuance call — delivery_id is
not read by the v1 endpoints, and payment_transaction_hash is not read by the v2 endpoints.


Shared Lookups

Check Order Fees

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

Requires CardSu, CardCreate or CardView.

Path parameters:

ParameterDescription
{type}Plastic, Virtual or Metal. Case-insensitive — virtual and VIRTUAL both resolve
{country}Country the card is issued in, as an uppercase ISO 3166-1 alpha-2 code, e.g. GB

Response:

{
  "currency": "USD",
  "order_fee": 9.99,
  "estimated_payment_amounts": [
    {
      "amount": 9.99,
      "precise_amount": "9990000000000000000",
      "token_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976",
      "token_symbol": "WUSD",
      "rate": 1.0,
      "fee_amount": 0,
      "fee_precise_amount": "0"
    }
  ]
}
FieldDescription
currencyFiat currency the fee is denominated in. A fee configured in USDC is reported as USD
order_feeOrder fee in currency. Excludes delivery — see below
estimated_payment_amounts[]The fee converted into each supported stablecoin
estimated_payment_amounts[].precise_amountAmount in the token's smallest unit, as a string
estimated_payment_amounts[].rateRate applied for that token

order_fee is the card order fee only. For a plastic card the delivery fee is added on top, and the
invoice endpoint is what returns the combined total. Do not present order_fee as the amount the
corporation will pay for a physical card.

List Delivery Countries

GET /api/v1/cards/delivery/countries

Requires CardSu, CardCreate or CardView. Returns the countries a plastic card can be delivered to.

Response:

["GB", "DE", "FR", "ES", "IT"]

List Delivery Methods

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

Requires CardSu, CardCreate or CardView.

Path parameters:

ParameterDescription
{country}Delivery country, ISO 3166-1 alpha-2

Response:

[
  {
    "provider": "DHL",
    "fee": 15.0,
    "currency": "USD",
    "estimated_payment_amounts": [
      {
        "amount": 15.0,
        "precise_amount": "15000000000000000000",
        "token_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976",
        "token_symbol": "WUSD",
        "rate": 1.0
      }
    ]
  }
]
FieldDescription
providerCourier identifier. This is the value passed as delivery_provider
feeDelivery fee in currency
estimated_payment_amounts[]The delivery fee converted into each supported stablecoin

Flow v1: External Fee

Step 1: Create the Fee Invoice

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

Requires CardSu or CardCreate.

Request body:

{
  "token_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976",
  "delivery_provider": "DHL"
}
FieldTypeRequiredDescription
token_addressstringYesToken used to pay the fee. Must exist in GET /api/v1/config/tokens
delivery_providerstringConditionalRequired when {type} is not Virtual; omit for virtual cards

Response:

{
  "recipient_address": "0xAAFF0821A09A1Aac28B72dD3Ff410A7ea5FEb874",
  "payment_amount": {
    "amount": 24.99,
    "precise_amount": "24990000000000000000",
    "token_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976",
    "token_symbol": "WUSD",
    "rate": 1.0
  }
}
FieldDescription
recipient_addressAddress to transfer the fee to
payment_amountThe total to transfer — order fee plus delivery fee, converted to token_address

Step 2: Pay the Fee On-Chain

Transfer payment_amount.precise_amount of payment_amount.token_address to recipient_address from
the corporation wallet, and keep the transaction hash. Step 3 consumes it as
payment_transaction_hash.

Step 3: Issue the Card

POST /api/v1/cards/virtual
POST /api/v1/cards/plastic

Requires CardSu or CardCreate.

Request body — virtual:

{
  "card_name": "Marketing team card",
  "name_on_card": "Acme Inc IT",
  "employee_id": "64120850-73a1-4df5-a074-d463258c9deb",
  "payment_transaction_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}

Request body — plastic:

{
  "card_name": "Marketing team card",
  "name_on_card": "Acme Inc IT",
  "employee_id": "64120850-73a1-4df5-a074-d463258c9deb",
  "delivery_provider": "DHL",
  "payment_transaction_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  "delivery_address": {
    "line1": "10 Downing Street",
    "line2": "Flat 2",
    "city": "London",
    "state": "",
    "postal_code": "SW1A 2AA",
    "country": "GB"
  }
}
FieldTypeRequiredDescription
card_namestringNoDisplay name shown in the card list
name_on_cardstringNoName embossed on the card. Overridden when the linked employee has complete personal data
employee_idstringNoEmployee to link the card to. Must belong to this corporation
payment_transaction_hashstringConditionalRequired when fees are enforced; omit otherwise
delivery_providerstringConditionalPlastic only. Required when fees are enforced. Defaults to DHL when fees are not enforced
delivery_addressobjectYes for plasticWhere the card is shipped
delivery_address.line1stringYesHouse number and street
delivery_address.line2stringNoApartment, suite, floor
delivery_address.citystringYesCity
delivery_address.statestringNoState or province
delivery_address.postal_codestringYesPostal code
delivery_address.countrystringYesUppercase ISO 3166-1 alpha-2

Response:

{
  "id": "64120850-73a1-4df5-a074-d463258c9deb"
}
FieldDescription
idThe card id. Every card endpoint takes it as {cardId}

Flow v2: Direct Fee

Step 1: Create the Fee Invoice

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

Requires CardSu or CardCreate. The request body is identical to v1.

Response:

{
  "delivery_id": 12345,
  "recipient_address": "0xAAFF0821A09A1Aac28B72dD3Ff410A7ea5FEb874",
  "payment_amount": {
    "amount": 24.99,
    "precise_amount": "24990000000000000000",
    "token_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976",
    "token_symbol": "WUSD",
    "rate": 1.0
  }
}
FieldDescription
delivery_idInteger reference for the issuance call. Single-use
recipient_addressThe platform Buffer contract address the fee settles into
payment_amountOrder fee plus delivery fee, converted to token_address

delivery_id is consumed by the issuance call, not by the invoice call. A failed issuance
attempt that got past validation may leave it spent. Do not retry a failed v2 issuance with the same
delivery_id — create a new invoice.

Step 2: Issue the Card

POST /api/v2/cards/virtual
POST /api/v2/cards/plastic

Requires CardSu or CardCreate.

Request body — virtual:

{
  "card_name": "Marketing team card",
  "name_on_card": "Acme Inc IT",
  "employee_id": "64120850-73a1-4df5-a074-d463258c9deb",
  "delivery_id": 12345
}

Request body — plastic:

{
  "card_name": "Marketing team card",
  "name_on_card": "Acme Inc IT",
  "employee_id": "64120850-73a1-4df5-a074-d463258c9deb",
  "delivery_provider": "DHL",
  "delivery_id": 12345,
  "delivery_address": {
    "line1": "10 Downing Street",
    "line2": "Flat 2",
    "city": "London",
    "state": "",
    "postal_code": "SW1A 2AA",
    "country": "GB"
  }
}
FieldTypeRequiredDescription
delivery_idintegerConditionalRequired when fees are enforced, and must be positive. Ignored when fees are not enforced
Other fieldsAs in v1, except that payment_transaction_hash is not used

Response:

{
  "id": "64120850-73a1-4df5-a074-d463258c9deb"
}

Code

const response = await fetch(`${baseUrl}/api/v2/cards/virtual`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${corporationToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    card_name: 'Marketing team card',
    employee_id: employeeId,
    delivery_id: deliveryId  // omit if fees not enforced
  })
});
const { id: cardId } = await response.json();
response = requests.post(
    f"{base_url}/api/v2/cards/virtual",
    headers={
        "Authorization": f"Bearer {corporation_token}",
        "Content-Type": "application/json",
    },
    json={
        "card_name": "Marketing team card",
        "employee_id": employee_id,
        "delivery_id": delivery_id,  # omit if fees not enforced
    },
)
card_id = response.json()["id"]
body, _ := json.Marshal(map[string]interface{}{
    "card_name":   "Marketing team card",
    "employee_id": employeeID,
    "delivery_id": deliveryID, // omit if fees not enforced
})

req, _ := http.NewRequest("POST", baseURL+"/api/v2/cards/virtual", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+corporationToken)
req.Header.Set("Content-Type", "application/json")

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

var issueResp struct {
    Id string `json:"id"`
}
json.NewDecoder(resp.Body).Decode(&issueResp)

Linking a Card to an Employee

employee_id is optional. It has three consequences, and all three are decided at issuance:

  1. Visibility. An employee without Su or CardSu sees only the cards linked to them. An
    unlinked card is invisible to everyone but a card administrator.
  2. The name on the card. The embossed name becomes the employee's
    "<first_name> <last_name>", truncated to 25 characters, and the name_on_card field in the
    request is ignored. This applies whenever the employee record carries first_name,
    last_name, email and phone — all four are required at invite, so it applies to every
    employee. name_on_card is used as sent only when no employee_id is linked.
  3. The billing address. With a complete employee record, the cardholder payload also carries the
    corporation's legal address as the billing address. A corporation with no legal address on record
    fails issuance with 400 ErrorMissingField and
    Failed to determine billing address for the card.

The employee is validated before the fee is charged, so a foreign or unknown employee_id fails
without spending delivery_id. An employee_id from another corporation returns 401 with
Employee does not belong to corporation.

To emboss a corporate name such as Acme Inc IT, leave employee_id unset. A linked employee
always overrides name_on_card, and the card is then invisible to everyone without CardSu — that
is the trade-off, and it cannot be changed after issuance.


Field Validation

FieldRequiredValidation (regex)Notes
card_nameNo^[a-zA-Z0-9]+[a-zA-Z0-9\-':+#& ]{1,50}$Must start alphanumeric; 2–51 characters overall
name_on_cardNo^[A-Za-z][A-Za-z .'-]{1,25}$Starts with a letter; 2–26 characters. No digits
delivery_address.line1, .line2Yes / No^[A-Za-z0-9&.,'’\-/() :+#]{2,100}$2–100 characters
delivery_address.cityYes^[A-Za-z\s\-'.]{2,100}$Letters only — a city containing a digit is rejected
delivery_address.stateNo^[A-Za-z][A-Za-z\s\-']{1,99}$Letters only
delivery_address.postal_codeYes^[A-Za-z0-9\s\-]{3,12}$3–12 characters
delivery_address.countryYes^[A-Z]{2}$Uppercase ISO 3166-1 alpha-2

postal_code is always required, including for countries that have no postal code. Send a
placeholder that matches the pattern, e.g. "00000". Only the format is validated, not real postal
validity.

The field is postal_code on the way in and zip_code on the way out. The issuance request takes
delivery_address.postal_code; the card returned by GET /api/v1/cards reports the same value as
delivery_address.zip_code. Map between them explicitly.

Address validation is format-only. An address that passes here can still be rejected by the card
processor, in which case the issuance call returns a synchronous 400 — no card is created and no card
webhook is sent.


What Happens Next

  1. The card is created in status Requested.
  2. A virtual card moves to Active once the processor completes issuance. A plastic card moves to
    NotActivated and must be activated on arrival — see Managing a Card.
  3. Each transition is delivered to POST {your_webhook_base_url}/v2/webhooks/cards.
  4. card_data.card_number_first_4 is present while the card is Requested or NotActivated;
    card_number_last_4 and expiry_date appear once it is past those states.

Error Handling

{
  "error_reason": "ErrorNotSupported",
  "error_description": "You have no card order/delivery fees to pay",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "field", "details": "fees_enforced" }
  ]
}

Validation Errors (400)

Error ReasonError DetailsDescriptionResolution
ErrorNotSupportedfield: fees_enforcedYou have no card order/delivery fees to payFees are not enforced. Skip the fee endpoints and issue directly
ErrorNotSupportedfield: company_idCard issuance is not supported for this companyNo card configuration exists for your company. Contact Wirex
ErrorNotFoundfield: type, field: countryNo fees found for specified country and methodThe format is not offered in that country
ErrorMissingFieldfield: delivery_providerdelivery provider is required for physical cardsSend a provider value from GET /api/v1/cards/delivery/methods/{country}
ErrorInvalidFieldfield: typeInvalid value for typeUse Plastic, Virtual or Metal
ErrorInvalidFieldfield: countryInvalid format for countryUppercase two-letter code
ErrorMissingFieldfield: delivery_address.line1Delivery address incompleteSend every required address field
ErrorInvalidFieldfield: delivery_address.cityCity contains disallowed charactersLetters, spaces, hyphens, apostrophes and periods only
ErrorGeneraldelivery_idDelivery ID is required and must be positiveCreate a v2 invoice and pass its delivery_id
ErrorGeneralcard_nameCards name is invalidMatch the card_name pattern
ErrorGeneralname_on_cardName on card is invalidMatch the name_on_card pattern
ErrorMissingFieldFailed to determine billing address for the cardThe corporation has no legal address. Complete KYB
ErrorGeneralCapability is not activeVisaVirtualCard or VisaPlasticCard is not Active

Authorization Errors (401, 403)

HTTPError ReasonDescriptionResolution
401ErrorGeneralEmployee does not belong to corporationThe employee_id belongs to another corporation
403ErrorPermissionDeniedUser does not have required permissionsIssuance requires CardSu or CardCreate

Server Errors (500)

Error ReasonDescriptionResolution
ErrorNotFoundOrder fees not foundNo fee configuration for that format and country
ErrorNotFoundDelivery fees not foundNo delivery configuration for that country and provider
ErrorGeneralRequested payment token not foundtoken_address is not in the catalogue
ErrorGeneralRate for requested payment token not foundNo rate for the fee currency against that token. Choose another token
ErrorGeneralFailed to process delivery paymentThe delivery_id was rejected — expired, already used, or unfunded. Create a new invoice
ErrorGeneralFailed to issue virtual card / Failed to issue plastic cardThe processor rejected the order. No card was created and no webhook is sent
ErrorGeneralFailed to link card to employeeThe card was issued but not linked. Read GET /api/v1/cards as a card administrator to find it
ErrorConfigurationInvalidCard configuration is missingPlatform configuration error. Contact Wirex

Failed to link card to employee means the card exists. It is not visible to the employee and
cannot be re-linked through the API — there is no link endpoint. Retrying issuance creates a second
card.


Did this page help you?