Card issuance invoice: pricing and eligibility for this product and member
x-on-behalf-of required
Use this endpoint for the card application confirmation page. Without it, end users would not see the price until POST /v1/cards/applications had already debited their funds.
Its role differs from GET /v1/cards/products: that endpoint is the catalog, without member context, for the available-cards screen; this endpoint is the invoice.
── Keep issue_fee and shipping_fee separate; do not combine them into one price ──
- Show the fees as separate rows. If shipping is hidden in the issuance fee, users see a physical card priced at 20 USD without knowing that part of it pays for shipping.
- The Free shipping label must depend on
shipping_fee == 0. Hardcoding it promises something that our administration settings can change at any time.
For virtual cards, shipping_fee is always "0" because nothing is shipped.
── shipping_by_address: shipping fees depend on the destination country ── Present only for physical cards. When a user changes the address on the invoice page, update the shipping fee and total immediately, without another request.
The shipping country is used only to calculate shipping fees; it does not affect issuance country restrictions. Any existing address belonging to the member may be selected. In shipping_by_address, deliverable is always true and reason is empty. Product country allowlists and blocklists check only the KYC used for this application (standard or quick KYC), for both virtual and physical cards. If the KYC is restricted, product-level can_apply is false.
── Read can_apply together with reason ── A bare false would leave you with only a generic Unavailable message, but these cases require different actions: missing KYC requires identity verification; card_address_required requires adding a shipping address; upstream unavailability requires a Try again later message.
Path Parameters
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Required | Card product ID returned by GET /v1/cards/products. Accepted with or without the cpd_ prefix. |
Query Parameters
| Field | Type | Required | Description |
|---|---|---|---|
quick_kyc_binding_id |
string | Optional | A quick KYC binding ID belonging to the current member, consistent with the card application. L0 members may explicitly select it for pricing and eligibility checks against that profile; it does not change their own verification level. |
Request Headers
| Field | Type | Required | Description |
|---|---|---|---|
x-on-behalf-of |
string | Required |
Response
{
"product_id": "cpd_12",
"form_factor": "physical",
"fee_asset": "USDT",
"ledger_scale": 6,
"display_scale": 2,
"issue_fee": "20.00",
"shipping_fee": "8.00",
"can_apply": true,
"reason": "",
"shipping_by_address": [
{
"address_id": "sad_6b1f0c72-9a3e-4d15-8f27-c4e0b9a31d68",
"country": "HK",
"is_default": true,
"shipping_fee": "8.00",
"total": "28.00",
"deliverable": true,
"reason": ""
}
]
}not_found: the product does not exist, is disabled, or is not available to you.curl -X GET 'https://api.zinfra.vip/v1/cards/products/{id}/quote' \
-H 'x-auth-token: Bearer $TOKEN' \
-H 'x-on-behalf-of: $MEMBER_ID'const res = await fetch("https://api.zinfra.vip/v1/cards/products/{id}/quote", {
method: "GET",
headers: {
"x-auth-token": "Bearer $TOKEN",
"x-on-behalf-of": "$MEMBER_ID",
},
});
// Keep monetary amounts as strings, never numbers.
const data = await res.json();import requests
res = requests.get(
"https://api.zinfra.vip/v1/cards/products/{id}/quote",
headers={
"x-auth-token": "Bearer $TOKEN",
"x-on-behalf-of": "$MEMBER_ID",
},
)
# Use Decimal(str(...)) for amounts, not float.
data = res.json()req, _ := http.NewRequest("GET", "https://api.zinfra.vip/v1/cards/products/{id}/quote",
nil)
req.Header.Set("x-auth-token", "Bearer $TOKEN")
req.Header.Set("x-on-behalf-of", "$MEMBER_ID")
res, err := http.DefaultClient.Do(req)
// Decode amount fields as string, not float64.HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.zinfra.vip/v1/cards/products/{id}/quote"))
.header("x-auth-token", "Bearer $TOKEN")
.header("x-on-behalf-of", "$MEMBER_ID")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
// Use String / BigDecimal for amounts, not double.$ch = curl_init('https://api.zinfra.vip/v1/cards/products/{id}/quote');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'x-auth-token: Bearer $TOKEN',
'x-on-behalf-of: $MEMBER_ID',
],
]);
$res = curl_exec($ch);
// Use bcmath / strings for amounts, not floatval.
{
"product_id": "cpd_12",
"form_factor": "physical",
"fee_asset": "USDT",
"ledger_scale": 6,
"display_scale": 2,
"issue_fee": "20.00",
"shipping_fee": "8.00",
"can_apply": true,
"reason": "",
"shipping_by_address": [
{
"address_id": "sad_6b1f0c72-9a3e-4d15-8f27-c4e0b9a31d68",
"country": "HK",
"is_default": true,
"shipping_fee": "8.00",
"total": "28.00",
"deliverable": true,
"reason": ""
}
]
}