Issue a card
curl --request POST \
--url https://api.fyatu.com/api/v3.20/cards \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"cardholderId": "chl_01HXYZ1234ABCDEF5678",
"productId": "prd_01HXYZ1111ABCDEF0001",
"amount": 100,
"customName": "Travel Card"
}
'import requests
url = "https://api.fyatu.com/api/v3.20/cards"
payload = {
"cardholderId": "chl_01HXYZ1234ABCDEF5678",
"productId": "prd_01HXYZ1111ABCDEF0001",
"amount": 100,
"customName": "Travel Card"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
cardholderId: 'chl_01HXYZ1234ABCDEF5678',
productId: 'prd_01HXYZ1111ABCDEF0001',
amount: 100,
customName: 'Travel Card'
})
};
fetch('https://api.fyatu.com/api/v3.20/cards', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fyatu.com/api/v3.20/cards",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'cardholderId' => 'chl_01HXYZ1234ABCDEF5678',
'productId' => 'prd_01HXYZ1111ABCDEF0001',
'amount' => 100,
'customName' => 'Travel Card'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.fyatu.com/api/v3.20/cards"
payload := strings.NewReader("{\n \"cardholderId\": \"chl_01HXYZ1234ABCDEF5678\",\n \"productId\": \"prd_01HXYZ1111ABCDEF0001\",\n \"amount\": 100,\n \"customName\": \"Travel Card\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.fyatu.com/api/v3.20/cards")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"cardholderId\": \"chl_01HXYZ1234ABCDEF5678\",\n \"productId\": \"prd_01HXYZ1111ABCDEF0001\",\n \"amount\": 100,\n \"customName\": \"Travel Card\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fyatu.com/api/v3.20/cards")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"cardholderId\": \"chl_01HXYZ1234ABCDEF5678\",\n \"productId\": \"prd_01HXYZ1111ABCDEF0001\",\n \"amount\": 100,\n \"customName\": \"Travel Card\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"status": 201,
"message": "Card issued",
"data": {
"id": "crd_01HXYZ5555ABCDEF1111",
"cardholderId": "chl_01HXYZ1234ABCDEF5678",
"productId": "prd_01HXYZ1111ABCDEF0001",
"status": "ACTIVE",
"cardType": "VIRTUAL",
"cardBrand": "VISA",
"maskedPan": "445123******4123",
"last4": "4123",
"createdAt": "2026-05-26T10:00:00Z",
"updatedAt": "2026-05-26T10:00:00Z",
"nameOnCard": "Travel Card",
"expirationDate": "06/2030",
"balance": 100,
"currency": "USD",
"features": {
"has3DS": true,
"hasApplePay": false,
"hasGooglePay": false,
"hasJIT": false,
"hasSpendControl": true,
"hasMccControl": false,
"isReloadable": true,
"isOneTimeUse": false
},
"spendingLimit": 1000,
"spendingPeriod": "TRANSAMOUNT",
"billingAddress": {
"address": "1234 Main St",
"city": "New York",
"state": "NY",
"zipCode": "10001",
"country": "US"
}
},
"meta": {
"requestId": "req_01HXY123456ABCDEF",
"platform": "Fyatu CaaS",
"timestamp": "2026-05-26T10:00:00Z"
}
}Cards
Issue Card
Issue a new virtual card to a cardholder. POST /cards. Requires cards:write scope.
POST
/
cards
Issue a card
curl --request POST \
--url https://api.fyatu.com/api/v3.20/cards \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"cardholderId": "chl_01HXYZ1234ABCDEF5678",
"productId": "prd_01HXYZ1111ABCDEF0001",
"amount": 100,
"customName": "Travel Card"
}
'import requests
url = "https://api.fyatu.com/api/v3.20/cards"
payload = {
"cardholderId": "chl_01HXYZ1234ABCDEF5678",
"productId": "prd_01HXYZ1111ABCDEF0001",
"amount": 100,
"customName": "Travel Card"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
cardholderId: 'chl_01HXYZ1234ABCDEF5678',
productId: 'prd_01HXYZ1111ABCDEF0001',
amount: 100,
customName: 'Travel Card'
})
};
fetch('https://api.fyatu.com/api/v3.20/cards', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fyatu.com/api/v3.20/cards",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'cardholderId' => 'chl_01HXYZ1234ABCDEF5678',
'productId' => 'prd_01HXYZ1111ABCDEF0001',
'amount' => 100,
'customName' => 'Travel Card'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.fyatu.com/api/v3.20/cards"
payload := strings.NewReader("{\n \"cardholderId\": \"chl_01HXYZ1234ABCDEF5678\",\n \"productId\": \"prd_01HXYZ1111ABCDEF0001\",\n \"amount\": 100,\n \"customName\": \"Travel Card\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.fyatu.com/api/v3.20/cards")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"cardholderId\": \"chl_01HXYZ1234ABCDEF5678\",\n \"productId\": \"prd_01HXYZ1111ABCDEF0001\",\n \"amount\": 100,\n \"customName\": \"Travel Card\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fyatu.com/api/v3.20/cards")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"cardholderId\": \"chl_01HXYZ1234ABCDEF5678\",\n \"productId\": \"prd_01HXYZ1111ABCDEF0001\",\n \"amount\": 100,\n \"customName\": \"Travel Card\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"status": 201,
"message": "Card issued",
"data": {
"id": "crd_01HXYZ5555ABCDEF1111",
"cardholderId": "chl_01HXYZ1234ABCDEF5678",
"productId": "prd_01HXYZ1111ABCDEF0001",
"status": "ACTIVE",
"cardType": "VIRTUAL",
"cardBrand": "VISA",
"maskedPan": "445123******4123",
"last4": "4123",
"createdAt": "2026-05-26T10:00:00Z",
"updatedAt": "2026-05-26T10:00:00Z",
"nameOnCard": "Travel Card",
"expirationDate": "06/2030",
"balance": 100,
"currency": "USD",
"features": {
"has3DS": true,
"hasApplePay": false,
"hasGooglePay": false,
"hasJIT": false,
"hasSpendControl": true,
"hasMccControl": false,
"isReloadable": true,
"isOneTimeUse": false
},
"spendingLimit": 1000,
"spendingPeriod": "TRANSAMOUNT",
"billingAddress": {
"address": "1234 Main St",
"city": "New York",
"state": "NY",
"zipCode": "10001",
"country": "US"
}
},
"meta": {
"requestId": "req_01HXY123456ABCDEF",
"platform": "Fyatu CaaS",
"timestamp": "2026-05-26T10:00:00Z"
}
}Overview
Issue a virtual prepaid card to a cardholder under one of your programs. PassproductId — the program, card type, and spend limits are all derived from the product automatically.
Card issuance is asynchronous. A successful response means the request was accepted; the card may be returned with status
CREATING (no PAN yet) and becomes usable only once provisioning completes. If the response is delayed or times out, the card is still being created — poll Get Card until status is ACTIVE rather than treating the immediate response as final.- JIT-enabled products (
features.hasJIT: true): The card is funded on-demand at the point of each transaction.amountis optional. - Standard products (
features.hasJIT: false): The card requires an initial balance at issuance.amountis required.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
cardholderId | string | Yes | The cardholder to issue the card to |
productId | string | Yes | The card product — program, card type, and spend limits are derived from this |
amount | number | Conditional | Initial balance in USD. Required when the product is not JIT-enabled |
customName | string | No | Card label. Defaults to the cardholder’s full name |
Example
curl -X POST https://api.fyatu.com/api/v3.20/cards \
-H "Authorization: Bearer $FYATU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"cardholderId": "chl_01HXYZ1234ABCDEF5678",
"productId": "prd_01HXYZ1111ABCDEF0001",
"amount": 100.00,
"customName": "Travel Card"
}'
const resp = await fetch('https://api.fyatu.com/api/v3.20/cards', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.FYATU_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
cardholderId: 'chl_01HXYZ1234ABCDEF5678',
productId: 'prd_01HXYZ1111ABCDEF0001',
amount: 100.00,
customName: 'Travel Card'
})
});
const body = await resp.json();
const card = body.data;
console.log('Card ID:', card.id);
console.log('Expiry:', card.expirationDate);
console.log('Balance:', card.balance, card.currency);
import os, requests
resp = requests.post(
'https://api.fyatu.com/api/v3.20/cards',
headers={'Authorization': f'Bearer {os.environ["FYATU_API_KEY"]}'},
json={
'cardholderId': 'chl_01HXYZ1234ABCDEF5678',
'productId': 'prd_01HXYZ1111ABCDEF0001',
'amount': 100.00,
'customName': 'Travel Card'
}
)
card = resp.json()['data']
print('Card ID:', card['id'])
print('Expiry:', card['expirationDate'])
print('Balance:', card['balance'], card['currency'])
Success Response (201)
{
"success": true,
"status": 201,
"message": "Card issued",
"data": {
"id": "crd_01HXYZ5555ABCDEF1111",
"cardholderId": "chl_01HXYZ1234ABCDEF5678",
"productId": "prd_01HXYZ1111ABCDEF0001",
"status": "ACTIVE",
"cardType": "VIRTUAL",
"cardBrand": "VISA",
"maskedPan": "445123******4123",
"last4": "4123",
"createdAt": "2026-05-26T10:00:00Z",
"updatedAt": "2026-05-26T10:00:00Z",
"nameOnCard": "Travel Card",
"expirationDate": "06/2030",
"balance": 100.00,
"currency": "USD",
"features": {
"has3DS": true,
"hasApplePay": false,
"hasGooglePay": false,
"hasJIT": false,
"hasSpendControl": true,
"hasMccControl": false,
"isReloadable": true,
"isOneTimeUse": false
},
"spendingLimit": 1000.00,
"spendingPeriod": "TRANSAMOUNT",
"billingAddress": {
"address": "1234 Main St",
"city": "New York",
"state": "NY",
"zipCode": "10001",
"country": "US"
}
},
"meta": {
"requestId": "req_01HXY123456ABCDEF",
"platform": "Fyatu CaaS",
"timestamp": "2026-05-26T10:00:00Z"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
id | string | Card identifier (prefix crd_) |
cardholderId | string | The cardholder this card belongs to |
productId | string | The product the card was issued under |
status | string | ACTIVE when the card is ready, or CREATING while the provider is still provisioning it — see below |
cardType | string | VIRTUAL or PHYSICAL |
cardBrand | string | VISA or MASTERCARD |
maskedPan | string | BIN-masked PAN — first 6 digits + 6 stars + last 4 (e.g. 445123******4123). Empty while the card is CREATING |
last4 | string | Last 4 digits of the PAN. Empty while the card is CREATING |
createdAt | string | ISO 8601 creation timestamp |
updatedAt | string | ISO 8601 last-update timestamp |
nameOnCard | string | Name displayed on the card — customName if provided, otherwise the cardholder’s full name |
expirationDate | string | Card expiry in MM/YYYY format (e.g. 06/2030) |
balance | number | Current card balance in USD |
currency | string | Card currency (always USD) |
features | object | Card capability flags inherited from the product |
features.has3DS | boolean | 3D Secure enabled |
features.hasApplePay | boolean | Apple Pay tokenisation supported |
features.hasGooglePay | boolean | Google Pay tokenisation supported |
features.hasJIT | boolean | Just-In-Time funding enabled (no pre-fund required) |
features.hasSpendControl | boolean | Spend limit control active |
features.hasMccControl | boolean | MCC (merchant category) control active |
features.isReloadable | boolean | Card can be topped up via POST /cards/{id}/fund |
features.isOneTimeUse | boolean | Card terminates automatically after its first settled transaction |
spendingLimit | number | Spend cap per spendingPeriod in USD, as reported by the card provider. Absent on BINs that hold no per-card limit |
spendingPeriod | string | Period over which the spend cap applies — TRANSAMOUNT, DAILY, WEEKLY, or MONTHLY. Absent whenever spendingLimit is |
billingAddress | object | Billing address registered with the card provider |
billingAddress.address | string | Street address |
billingAddress.city | string | City |
billingAddress.state | string | State or region |
billingAddress.zipCode | string | Postal code |
billingAddress.country | string | Two-letter ISO 3166-1 country code |
Provisioning is asynchronous
The card provider provisions cards asynchronously. When you callPOST /cards, the card may come back with status: "CREATING" and empty maskedPan, last4, and expirationDate — provisioning can take anywhere from a few seconds up to ~1 hour. The 201 response is an acknowledgement that the card was accepted, not a guarantee the card number is ready.
If you store only the
cardId from the 201, do not read maskedPan/last4/expirationDate from that response — they may be blank. Use the CARD_ISSUED webhook (below) as the authoritative “card is ready” signal: it fires once, when provisioning completes, and always carries the finalized maskedPan, last4, and expirationDate. You can also poll GET /cards/{id} — a CREATING card returns ACTIVE with full details once ready.Webhook
TheCARD_ISSUED event is fired when the card finishes provisioning (CREATING → ACTIVE) — immediately for instantly-provisioned cards, or later (up to ~1h) for cards that were CREATING. It always includes the complete card details:
{
"event": "CARD_ISSUED",
"eventId": "evt_01HXY123456ABCDEF",
"businessId": "BUS1A2B3C4D5E6F",
"environment": "LIVE",
"timestamp": "2026-05-26T10:00:00Z",
"data": {
"cardId": "crd_01HXYZ5555ABCDEF1111",
"status": "ACTIVE",
"cardType": "VIRTUAL",
"cardBrand": "VISA",
"cardholderId": "chl_01HXYZ1234ABCDEF5678",
"maskedPan": "445123******4123",
"last4": "4123",
"expirationDate": "06/2030",
"balance": 100.00,
"currency": "USD",
"is3ds": true,
"isTokenized": false,
"isJitfEnabled": false,
"isOneTimeUse": false
}
}
Who Can Hold a Card
Two things about the cardholder are checked before a card is issued. Verification. Some BINs require a verified cardholder and some require none; BINs on one programme differ.GET /cardholders/{id} returns programEligibility, which says which BINs are
open to a given cardholder and what to do where one is not.
Country. Some products cannot be issued to cardholders from particular countries. Both
nationality and country of residence are checked, and a match returns
422 CARDHOLDER_COUNTRY_RESTRICTED naming the country.
Cards That Are Still Provisioning
A201 means the card was accepted, not that it exists at the provider yet. On some BINs
provisioning is immediate and the response carries everything; on others the card comes back as
"status": "CREATING" with maskedPan and last4 empty and no expirationDate.
Do not treat a missing PAN as a failure. Wait for the card.created webhook, or poll
GET /cards/{id} until status becomes ACTIVE.
Code that reads
maskedPan, last4, expirationDate or spendingLimit straight from the
create response will break on a card that is still provisioning. Read them once the card is
ACTIVE.Spending Limits
Spending limits belong to the product, which falls back to the program when it sets none. There is no per-card limit on this endpoint — a third place to set one would only give the three a way to disagree. To change what a card may spend, change the product, or usePOST /cards/{id}/limits where the BIN supports per-card limits. Some do not, and answer
422 CARD_LIMITS_UNSUPPORTED; for those the card balance is the spending ceiling.
Error Codes
| Code | HTTP | Cause |
|---|---|---|
CARD_AMOUNT_REQUIRED | 400 | amount was not provided for a non-JIT product |
INVALID_REQUEST | 400 | Missing required field or unsupported card type |
CARDHOLDER_NOT_FOUND | 404 | Cardholder does not exist or belongs to another business |
PRODUCT_NOT_FOUND | 404 | Product does not exist or belongs to another business |
PROGRAM_NOT_FOUND | 404 | Program linked to the product not found |
PRODUCT_INACTIVE | 422 | Product is not active |
PROGRAM_INACTIVE | 422 | Program is not active |
CARDHOLDER_INACTIVE | 422 | Cardholder is suspended or terminated |
CARDHOLDER_KYC_NOT_APPROVED | 422 | The cardholder is not in a state this product’s programme accepts — see the eligibility table on GET /cardholders/{id} |
CARDHOLDER_COUNTRY_RESTRICTED | 422 | Cards cannot be issued to cardholders from that country on this product. The message names the country; it matches on nationality or country of residence |
CARDHOLDER_CARD_LIMIT_EXCEEDED | 409 | Cardholder has reached the maxCardsPerCardholder limit for this product |
PROVIDER_CREATE_FAILED | 422 | Card creation was rejected by the card provider |
CARD_CREATION_UNAVAILABLE | 503 | Card creation is temporarily unavailable — either paused by Fyatu, or a transient processing issue on our side. Retry later. |
INSUFFICIENT_SCOPE | 403 | Key lacks cards:write scope |
INTERNAL_ERROR | 500 | Server error |
Authorizations
API key from the FYATU CaaS portal. Pass as Authorization: Bearer <key>.
Body
application/json
The cardholder to issue the card to
Example:
"chl_01HXYZ1234ABCDEF5678"
The card product — card type and program are derived from this
Example:
"prd_01HXYZ1111ABCDEF0001"
Initial balance in USD. Required for non-JIT products; optional for JIT-enabled products.
Example:
100
Card label. Defaults to the cardholder's full name when omitted.
Example:
"Travel Card"
Response
Card issued

