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

Create Intent

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

​​ Overview

After card enrollment is complete, call this endpoint to initiate a payment. Provide the transaction details (amount, currency, merchant info). Depending on the network and the enrollment state, the response is either a challenge the cardholder must complete in an iframe (then finalized with Intent Challenge), or the intent_id directly — ready for order placement with no further call.

  • Enrollment required: The flow must be fully enrolled (a virtual_card_id was returned from Verify Enrollment or Select Card) before creating an intent
  • Two response shapes: { flow_token, challenge } when cardholder authorization is required (Visa; Mastercard by default), or { flow_token, intent_id } when it is not (Discover always; Mastercard when the cardholder authenticated at enrollment moments earlier and that proof is still fresh). Branch on which key is present.
  • Mandate details: Transaction amount, currency, and merchant information are captured in the mandate object

​​ Authentication

  • x-firmly-authorization (string, required) — API token for authenticating the request

​​ Request Body

  • flow_token (string, required) — Flow token from a completed enrollment — the flow must be fully enrolled (a virtual_card_id was returned).

  • secure_token (string) — Secure token from a prior iframe handshake. Required if the enrollment used an iframe handshake.

  • mandate (object, required) — Transaction details for the payment intent Required fields:

    • amount (string): Transaction amount (e.g., "585.49")
    • currency_code (string): ISO 4217 currency code (e.g., "USD") Optional fields:
    • currency_numeric (string): ISO 4217 numeric currency code (e.g., "840")
    • merchant_name (string): Display name for the merchant
    • merchant_category (string): Merchant category description
    • merchant_category_code (string): Merchant Category Code (MCC)
    • description (string): Transaction description shown to the cardholder
    • consumer_prompt (string): Custom message displayed during FIDO assertion
    • quantity (number): Item quantity
    • ttl_seconds (number): Time-to-live for the intent in seconds
    • callback_uri (string): Callback URI passed to the FIDO iframe

​​ Response

The response is one of two shapes. Exactly one of challenge or intent_id is present.

Challenge required (Visa; Mastercard by default):

  • flow_token (string) — Refreshed flow token for the subsequent Intent Challenge call

  • challenge (object) — FIDO assertion challenge for cardholder authorization Properties:

    • type (string): Challenge type, typically embed_iframe
    • uri (string): Iframe URI for the FIDO assertion flow
    • iframeContext (object): Additional context for configuring the iframe (when present). This object is passed through from the card network, so its keys follow the network’s own casing rather than Firmly’s snake_case convention.

No challenge (Discover always; Mastercard when a fresh enrollment authentication can be reused):

  • flow_token (string) — Refreshed flow token to pass to Wallet Complete Order

  • intent_id (string) — Intent identifier for order placement. Skip /intent/challenge and place the order directly.

​​ Code Examples


curl --request POST \
--url https://api.firmly.work/api/v1/wallets/agentic-pay/intent \
--header 'Content-Type: application/json' \
--header 'x-firmly-authorization: YOUR_TOKEN' \
--data '{
"flow_token": "eyJhbGciOiJBMjU2S1ci...",
"mandate": {
"amount": "585.49",
"currency_code": "USD",
"merchant_name": "Example Store",
"description": "Order #12345"
}
}'

const response = await fetch('https://api.firmly.work/api/v1/wallets/agentic-pay/intent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-firmly-authorization': 'YOUR_TOKEN'
},
body: JSON.stringify({
flow_token: 'eyJhbGciOiJBMjU2S1ci...',
mandate: {
amount: '585.49',
currency_code: 'USD',
merchant_name: 'Example Store',
description: 'Order #12345'
}
})
});
const { flow_token, challenge } = await response.json();
// Render the FIDO assertion iframe using challenge.uri
console.log('Challenge URI:', challenge.uri);

import requests
response = requests.post(
'https://api.firmly.work/api/v1/wallets/agentic-pay/intent',
headers={
'Content-Type': 'application/json',
'x-firmly-authorization': 'YOUR_TOKEN'
},
json={
'flow_token': 'eyJhbGciOiJBMjU2S1ci...',
'mandate': {
'amount': '585.49',
'currency_code': 'USD',
'merchant_name': 'Example Store',
'description': 'Order #12345'
}
}
)
result = response.json()
print('Challenge URI:', result['challenge']['uri'])

​​ Response Examples

Challenge required:


{
"flow_token": "<encrypted-jwe-token>",
"challenge": {
"type": "embed_iframe",
"uri": "https://sbx.vts.auth.visa.com/vts-auth/authenticate?apiKey=...",
"iframeContext": {
"endpoint": "L29hdXRoMi9hdXRob3JpemF0aW9u",
"identifier": "59c3208baeadec4566181b18356a5e02",
"payload": "eyJraWQiOiI0ZDkxZWY5MiIsImFsZyI6IlJTMjU2In0",
"action": "AUTHENTICATE"
}
}
}

No challenge — intent_id returned directly:


{
"flow_token": "<encrypted-jwe-token>",
"intent_id": "1-5C90F1500800b0be2dc0-e6cf-55d5-54e6-12d8519fad02"
}

​​ Error Responses

Code Status Description
BadRequest 400 Invalid flow_token format or missing required mandate fields
BadRequest 400 Card not enrolled — the flow is not fully enrolled (no virtual_card_id was returned)
ErrorServiceUnavailable 503 Network intent creation failed or timed out