Authorize and capture
A two-step payment: first authorize the card to reserve funds, then capture to settle them. Use this when you need to confirm funds are available before you commit the charge, such as verifying an order before shipping.
The key difference from a standard sale: set type: "authorize" instead of "sale". The authorization reserves funds without taking them. You capture later by creating a second transaction with type: "sale".
Flow overview
The authorize is the customer-initiated leg: the customer is at checkout, so it runs on a fresh token. It returns a payment instrument, and the later capture runs on that instrument as a merchant-initiated charge. See Card on file.
Tokenization is a prerequisite step that differs between environments and integration paths. The authorize and capture calls themselves are identical across paths once you have the token.
Stage flow
On stage there is no production tokenization host. Tokenize the card through the staging tokenization endpoint, then run authorize and capture.
See Tokenization endpoint for the stage body shape.
Production flow — FramePay
For web integrations on production, the recommended path is FramePay. Card data is collected by hosted iframe fields, never touches your server, and your PCI scope stays at SAQ A.
See FramePay for the SDK integration.
Production flow — server-side tokenization endpoint
For native mobile apps or backend-only integrations on production, the card data is tokenized through the dedicated production tokenization host (tokenization.payments.ai). Raw card data flows through your server, placing your PCI scope at SAQ D.
See When to use FramePay vs server-side for the choice between paths and Tokenization endpoint for the production body shape.
Prerequisites
- A customer record. See Create customer with payment instrument.
- A fresh tokenization token for the card being authorized.
Set your environment variables first. The POST /transactions endpoint sits under the Public API, so the host is staging-api.payments.ai on stage and api.payments.ai on production. Tokenization (the step that produces the token) does not share this host pattern. Stage and production tokenization use different hosts, paths, and request body shapes. See the Flow overview above for the three integration paths and Tokenization endpoint for the body differences between environments.
Stage:
export API_URL="staging-api.payments.ai"
export API_KEY="Your API Key Here"
export ORGANIZATION_ID="Your Organization ID Here"
export CUSTOMER_ID="Customer ID Here"
Production:
export API_URL="api.payments.ai"
export API_KEY="Your API Key Here"
export ORGANIZATION_ID="Your Organization ID Here"
export CUSTOMER_ID="Customer ID Here"
The curl examples below use ${API_URL} because POST /transactions shares one structure across environments — only the host changes. This is not the case for the tokenization step.
1. Tokenize the card
The authorize runs on a fresh token, so tokenize the card first. The call differs by environment and integration path. See the Flow overview above and Tokenization endpoint for the body shapes.
Keep the returned token id for the next step. It is single-use and the authorize consumes it.
2. Authorize the transaction
Setting type: "authorize" reserves funds without capturing them. This is the customer-initiated leg, so it sends the fresh token with isMerchantInitiated: false.
Note two values from the response: the transaction id, which you need to track the authorization, and paymentInstrument.id, which the capture uses. Both are present already at status: "waiting", with or without a redirectUrl.
The authorize response is not an approval; it returns result: "unknown" until resolved. See Card on file.
curl -X POST \
"https://${API_URL}/v1/public-api/organizations/${ORGANIZATION_ID}/transactions" \
--header "Content-Type: application/json" \
--header "Authorization: ApiKey ${API_KEY}" \
--data-raw '{
"type": "authorize",
"customerId": "Customer ID Here",
"currency": "USD",
"amount": 1,
"paymentInstruction": {
"token": "Token value from tokenization step"
},
"isMerchantInitiated": false,
"redirectUrl": "https://yoursite.com/{id}/{result}"
}'
3. Capture the transaction
To settle the reserved funds, create a new transaction with type: "sale" using the same customerId and the paymentInstrument.id returned by the authorize. The customer is no longer present, so this leg is merchant-initiated.
curl -X POST \
"https://${API_URL}/v1/public-api/organizations/${ORGANIZATION_ID}/transactions" \
--header "Content-Type: application/json" \
--header "Authorization: ApiKey ${API_KEY}" \
--data-raw '{
"type": "sale",
"customerId": "Customer ID Here",
"currency": "USD",
"amount": 1,
"paymentInstruction": {
"paymentInstrumentId": "PaymentInstrumentID from the authorize response"
},
"isMerchantInitiated": true
}'
Authorization expiry
The issuing bank holds reserved funds for a limited window set by the card scheme. If you do not capture within the window, the hold expires automatically and the funds are released back to the customer. Typical card-scheme defaults:
| Scheme | Typical hold window |
|---|---|
| Visa | ~7 days |
| Mastercard | ~30 days |
| American Express | ~7 days |
| Discover | ~10 days |
These are card-scheme defaults rather than Payments AI rules. The actual window depends on the issuing bank and merchant category. Treat them as planning guidance, not contractual guarantees. If you need to release an authorization before its window expires, contact your Integration Consultant, as the public API does not currently expose a void endpoint.
Capture amount
The merchant guide and Payments AI's documented flow show the capture (sale) amount matching the original authorization. Partial capture (capturing less than the authorized amount and releasing the difference) is not currently documented for the public API. If you need it, confirm the behavior with your Integration Consultant before relying on it.
Notes
- The capture must use the same
customerIdand thepaymentInstrument.idthe authorization returned. Payments AI does not let you capture an authorization against a different payment instrument. - The authorize returns
status: "waiting"withresult: "unknown", the same non-final pair as any other customer-initiated charge. It is not an approval; resolve the final result before you capture. A successful capture returnsstatus: "completed". - The original authorize transaction stays on record alongside the capture.
- You send the capture as
type: "sale", but Payments AI reclassifies it: the stored transaction comes back astype: "capture"with aparentTransactionIdpointing at the authorization. Readtypefrom the response rather than assuming it echoes what you sent.