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.emailis mandatory when the card is a Visa card (the network is auto-detected from the BIN) — Visa enrollment is rejected with400 BadRequestif it is missing. It is optional for Mastercard. Fields:first_name(string) — Cardholder first namelast_name(string) — Cardholder last nameemail(string) — Cardholder email address (required for Visa)phone(string) — Cardholder phone numbercountry_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 PANmasked.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 onlymasked.issuer_name(string) — Issuing bank name — Mastercard only
-
verification_methods(array) — Available verification challenges for cardholder authenticationverification 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 includinguriandnext_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 requestsresponse = 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"}
Related Endpoints
- Trigger Enrollment — Trigger a verification challenge after enrollment
- Agentic Pay Overview — Concepts and full endpoint list