Z Zise Developers 简体中文

Change the billing address (used for AVS checks by US online merchants)

PATCH /v1/cards/{id}/billing-address scope: cards:write
On behalf of a member · 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_updatable in card details).

Path Parameters

FieldTypeRequiredDescription
id string Required Card ID
FieldTypeRequiredDescription
x-idempotency-key string Required
x-on-behalf-of string Required The member on whose behalf to call.

Request Body

FieldTypeRequiredDescription
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

200Confirmed by the upstream
{
  "id": "crd_9f2c1b7a-3d51-4a2e-9c08-6b1f0d4e77aa",
  "billing_address": {
    "address": "1600 Amphitheatre Pkwy",
    "city": "Mountain View",
    "state": "CA",
    "country": "US",
    "postal_code": "94043"
  }
}
400invalid_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.
404not_found: the card does not exist or does not belong to this member.
502upstream_error: outcome unknown; not persisted locally. Retry with the same idempotency key.
Request
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.
200
{
  "id": "crd_9f2c1b7a-3d51-4a2e-9c08-6b1f0d4e77aa",
  "billing_address": {
    "address": "1600 Amphitheatre Pkwy",
    "city": "Mountain View",
    "state": "CA",
    "country": "US",
    "postal_code": "94043"
  }
}