Docs
Firmly Agentic Commerce
Set theme to dark (⇧+D)

Enroll Card

POST https://api.firmly.work/api/v1/wallets/agentic-pay/enroll

​​ Overview

The Enroll Card endpoint initiates card enrollment by submitting card details to the appropriate network (Visa, Mastercard, or Discover). Firmly detects the network automatically from the card’s BIN. The response includes a flow_token for chaining subsequent calls, a virtual_card_id assigned by the network, and a list of verification_methods available for cardholder authentication.

The verification methods returned depend on the card network (detected automatically). These may include an iframe handshake to capture a secure_token, OTP challenges, or FIDO2 iframe-based verification.

​​ Authentication

  • x-firmly-authorization (string, required) — API token for server-to-server authentication

​​ Request Body

  • pan (string, required) — Card number (PAN)

  • expiry_month (string, required) — Two-digit expiry month (e.g., "03", "12")

  • expiry_year (string, required) — Four-digit expiry year (e.g., "2028")

  • cvv (string) — Card verification value

  • cardholder_full_name (string) — Full name as printed on the card

  • consumer (object) — Cardholder information for enrollment. Required for Visa: consumer.email is mandatory when the card is a Visa card (the network is auto-detected from the BIN) — Visa enrollment is rejected with 400 BadRequest if it is missing. It is optional for Mastercard. Fields:

    • first_name (string) — Cardholder first name
    • last_name (string) — Cardholder last name
    • email (string) — Cardholder email address (required for Visa)
    • phone (string) — Cardholder phone number
    • country_code (string) — ISO country code (e.g., "US", "GB")

​​ Response

  • flow_token (string) — JWE-encrypted state token required for all subsequent calls in this enrollment flow

  • virtual_card_id (string) — Network-assigned card identifier for the enrolled card

  • card_art (object) — Card art metadata for rendering the card image. Optional — may not be returned on re-enrollment of the same card. Destinations should persist this from the initial response. See Card Art for details.

  • masked (object) — Masked card details for rendering a saved-card UI. Destinations should persist this from the initial response for use with Select Card.

    masked properties
    • masked.last4 (string) — Last four digits of the funding PAN
    • masked.exp_month (string) — Expiry month (e.g., "12")
    • masked.exp_year (string) — Expiry year (e.g., "2027")
    • masked.brand (string) — Card brand ("visa", "mastercard")
    • masked.card_type (string) — Card type (e.g., "DEBIT", "CREDIT") — Mastercard only
    • masked.issuer_name (string) — Issuing bank name — Mastercard only

  • verification_methods (array) — Available verification challenges for cardholder authentication

    verification method properties
    • verification_methods[].id (string) — Method identifier (e.g., "OTP_SMS", "OTP_EMAIL", "MANAGED_AUTHENTICATION")
    • verification_methods[].type (string) — Method type: "embed_iframe_handshake", "embed_iframe", or "otp"
    • verification_methods[].attributes (object) — Method-specific data including uri and next_steps

​​ Code Examples


curl --request POST \
--url https://api.firmly.work/api/v1/wallets/agentic-pay/enroll \
--header 'Content-Type: application/json' \
--header 'x-firmly-authorization: YOUR_API_TOKEN' \
--data '{
"pan": "4111111111111111",
"expiry_month": "03",
"expiry_year": "2028",
"cvv": "123",
"cardholder_full_name": "Jane Doe",
"consumer": {
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"country_code": "US"
}
}'

const response = await fetch('https://api.firmly.work/api/v1/wallets/agentic-pay/enroll', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-firmly-authorization': 'YOUR_API_TOKEN'
},
body: JSON.stringify({
pan: '4111111111111111',
expiry_month: '03',
expiry_year: '2028',
cvv: '123',
cardholder_full_name: 'Jane Doe',
consumer: {
first_name: 'Jane',
last_name: 'Doe',
email: 'jane@example.com',
country_code: 'US'
}
})
});
const data = await response.json();
console.log(data.flow_token);
console.log(data.verification_methods);

import requests
response = requests.post(
'https://api.firmly.work/api/v1/wallets/agentic-pay/enroll',
headers={
'Content-Type': 'application/json',
'x-firmly-authorization': 'YOUR_API_TOKEN'
},
json={
'pan': '4111111111111111',
'expiry_month': '03',
'expiry_year': '2028',
'cvv': '123',
'cardholder_full_name': 'Jane Doe',
'consumer': {
'first_name': 'Jane',
'last_name': 'Doe',
'email': 'jane@example.com',
'country_code': 'US'
}
}
)
data = response.json()
print(data['flow_token'])
print(data['verification_methods'])

​​ Response Example


{
"flow_token": "<encrypted-jwe-token>",
"virtual_card_id": "cf90be5c86363de702ed19beec46e102",
"verification_methods": [
{
"id": "VISA_IFRAME_HANDSHAKE",
"type": "embed_iframe_handshake",
"attributes": {
"uri": "https://sbx.vts.auth.visa.com/vts-auth/authenticate?apiKey=...",
"next_steps": [
"Load this iframe in the cardholder browser",
"Capture sessionContext.secureToken from postMessage",
"Send to /enroll/trigger as secure_token"
]
}
}
],
"card_art": {
"network": "visa",
"url": "https://api.firmly.work/api/v1/wallets/agentic-pay/card-art/<signed-token>",
"background_color": "#1A1A2E",
"foreground_color": "#FFFFFF",
"descriptor": "Test Bank 2"
},
"masked": {
"last4": "1111",
"exp_month": "03",
"exp_year": "2028",
"brand": "visa"
}
}

​​ Error Responses

Error Code Status Description
MissingAuthHeader 400 The x-firmly-authorization header is absent or malformed (empty or wrong shape).
InvalidInputBody 400 Missing required fields (pan, expiry_month, expiry_year) or invalid format
InvalidAPIToken 400 The header was present and well-formed, but the API token it carried was rejected (invalid or revoked).
PaymentMethodNotAvailable 409 Card network not supported or not enabled for this destination
BadRequest 400 The card data is incomplete for the selected network — it passed schema validation but a required credential is missing
400 - Missing Auth Header

{
"code": 400,
"error": "MissingAuthHeader",
"description": "Missing or invalid x-firmly-authorization header"
}
422 - Invalid Input

{
"code": 400,
"error": "InvalidInputBody",
"description": "Request body validation failed: expiry_month is required"
}
400 - Invalid API Token

{
"code": 400,
"error": "InvalidAPIToken",
"description": "API token is invalid."
}
400 - Bad Request

{
"code": 400,
"error": "BadRequest",
"description": "Card data incomplete"
}
409 - Network Not Available

{
"code": 409,
"error": "PaymentMethodNotAvailable",
"description": "Card network is not supported or not enabled for this partner"
}