Change the billing address (used for AVS checks by US online merchants)
x-on-behalf-of required
The billing address is submitted with the card number to the acquirer for AVS matching; a mismatch causes a decline. If users move without updating it, the card will fail at merchants enforcing strict AVS checks.
⚠ First read billing_address_updatable from GET /v1/cards/{id}. Not every BIN permits changes; this is a card-network/issuer restriction, not a setting we control. Do not show this entry point when the field is false: the call returns product_not_available, which cannot meaningfully explain the issue to the end user.
⚠ 200 means the upstream confirmed the change. This endpoint is synchronous, unlike /limits, where 200 means only that we recorded it. With 502, the outcome is unknown: we do not persist the new address. Doing so would make the address we return differ from the address used by the card network for matching, and comparing those two is the only available diagnostic when AVS declines occur. Retry with the same idempotency key, then read the card details again to confirm.
⚠ state is optional, since many countries have no state/province level. country is validated only for shape, ISO-2 uppercase letters. We do not reject against our own country list, since an incorrect restriction would reject a user's genuine address.
Prerequisites
- The card is not
closed/expired/replaced. - The card's BIN permits billing address updates (
billing_address_updatablein card details).
Path Parameters
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Required | Card ID |
Request Headers
| Field | Type | Required | Description |
|---|---|---|---|
x-idempotency-key |
string | Required | |
x-on-behalf-of |
string | Required | The member on whose behalf to call. |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
address |
string | Required | Street address |
city |
string | Required | |
state |
string | Optional | State/province. Leave empty for countries without this administrative level. |
country |
string | Required | ISO 3166-1 alpha-2, uppercase. |
postal_code |
string | Required |
Response
{
"id": "crd_9f2c1b7a-3d51-4a2e-9c08-6b1f0d4e77aa",
"billing_address": {
"address": "1600 Amphitheatre Pkwy",
"city": "Mountain View",
"state": "CA",
"country": "US",
"postal_code": "94043"
}
}invalid_fields: required fields missing or country not ISO-2 (includes a fields array).
state_invalid: card closed or expired.
product_not_available: the card's BIN does not allow billing address changes or credentials are incomplete.
invalid_request: the upstream rejected the address.not_found: the card does not exist or does not belong to this member.upstream_error: outcome unknown; not persisted locally. Retry with the same idempotency key.curl -X PATCH 'https://api.zinfra.vip/v1/cards/{id}/billing-address' \
-H 'x-auth-token: Bearer $TOKEN' \
-H 'x-on-behalf-of: $MEMBER_ID' \
-H 'content-type: application/json' \
-d '{
"address": "1600 Amphitheatre Pkwy",
"city": "Mountain View",
"state": "CA",
"country": "US",
"postal_code": "94043"
}'const res = await fetch("https://api.zinfra.vip/v1/cards/{id}/billing-address", {
method: "PATCH",
headers: {
"x-auth-token": "Bearer $TOKEN",
"x-on-behalf-of": "$MEMBER_ID",
"content-type": "application/json",
},
body: JSON.stringify({
"address": "1600 Amphitheatre Pkwy",
"city": "Mountain View",
"state": "CA",
"country": "US",
"postal_code": "94043"
}),
});
// Keep monetary amounts as strings, never numbers.
const data = await res.json();import requests
res = requests.patch(
"https://api.zinfra.vip/v1/cards/{id}/billing-address",
headers={
"x-auth-token": "Bearer $TOKEN",
"x-on-behalf-of": "$MEMBER_ID",
"content-type": "application/json",
},
json={
"address": "1600 Amphitheatre Pkwy",
"city": "Mountain View",
"state": "CA",
"country": "US",
"postal_code": "94043"
},
)
# Use Decimal(str(...)) for amounts, not float.
data = res.json()req, _ := http.NewRequest("PATCH", "https://api.zinfra.vip/v1/cards/{id}/billing-address",
strings.NewReader(`{
"address": "1600 Amphitheatre Pkwy",
"city": "Mountain View",
"state": "CA",
"country": "US",
"postal_code": "94043"
}`))
req.Header.Set("x-auth-token", "Bearer $TOKEN")
req.Header.Set("x-on-behalf-of", "$MEMBER_ID")
req.Header.Set("content-type", "application/json")
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/{id}/billing-address"))
.header("x-auth-token", "Bearer $TOKEN")
.header("x-on-behalf-of", "$MEMBER_ID")
.header("content-type", "application/json")
.method("PATCH", HttpRequest.BodyPublishers.ofString("""
{
"address": "1600 Amphitheatre Pkwy",
"city": "Mountain View",
"state": "CA",
"country": "US",
"postal_code": "94043"
}
"""))
.build();
// Use String / BigDecimal for amounts, not double.$ch = curl_init('https://api.zinfra.vip/v1/cards/{id}/billing-address');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'x-auth-token: Bearer $TOKEN',
'x-on-behalf-of: $MEMBER_ID',
'content-type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"address": "1600 Amphitheatre Pkwy",
"city": "Mountain View",
"state": "CA",
"country": "US",
"postal_code": "94043"
}
JSON,
]);
$res = curl_exec($ch);
// Use bcmath / strings for amounts, not floatval.
{
"id": "crd_9f2c1b7a-3d51-4a2e-9c08-6b1f0d4e77aa",
"billing_address": {
"address": "1600 Amphitheatre Pkwy",
"city": "Mountain View",
"state": "CA",
"country": "US",
"postal_code": "94043"
}
}