Register a card
Register a debit card as a customer-owned External Account. Card numbers are accepted only on this dedicated endpoint; reads, lists and deletes stay unified on the other External Account routes. details.brand is derived server-side from the card number. Only debit cards are supported; other card types are rejected at provisioning. billingAddress is optional and falls back to the customer’s address. A per-customer velocity guard returns 429 TooManyCardAttempts (with a Retry-After header) against card-testing abuse.
Card input depends on the environment. Production live requires VGS-enabled card registration: details.cardNumber and details.cvv must be VGS aliases, and raw values are rejected with CardNumberInvalid or CVVInvalid. Collect real card data with the Polygon SDK’s hosted card form. The server-to-server VGS path for this endpoint is not available yet; do not send real card data directly to the API.
In sandbox environments with VGS disabled, send only test card data: details.cardNumber must contain 12–19 digits and details.cvv must contain 3–4 digits. Those environments reject VGS aliases. A VGS-enabled sandbox instead requires aliases, as in the example below. Confirm the sandbox’s input mode before integrating.
Uniqueness. A card already registered with the card provider returns 409 externalAccountAlreadyRegistered, WITHOUT details: the provider’s dedupe is global (it spans customers and partners), so the blocking registration is frequently not an account in this project and cannot be named.
Country screening. A billing country, or the owner’s KYC country, on the compliance prohibited-jurisdictions list is not a 4xx: the create returns 201 with status=rejected and failure.code=countryProhibited, and the card is never sent to the provider. If the owning customer’s address country is not a recognised country (so it cannot be screened), the create returns 422 billingAddressInvalid; correct the customer’s address.
Billing address lines. line1 and line2 must each be at most 35 UTF-16 code units (a supplementary character counts as two), must not be digits only or a PO Box, and must not contain any of !@#$%^&*()+=<>.,;:?"/\|}{[]~_. A supplied address that breaks a line rule returns 422 billingAddressInvalid naming billingAddress.line1 or line2. When billingAddress is omitted and the customer’s own address breaks one, the create returns 422 billingAddressInvalid naming the customer’s address (not missingBillingAddress); supply billingAddress or correct the customer’s address. Supplied nonblank lines are sent as given, including leading and trailing whitespace, which counts toward the limit. A line2 of only whitespace is treated as absent and not sent. The line rules apply only to an address that passes country screening: a prohibited country is still 201 with status=rejected.
Cardholder name. The card is registered under the owning customer’s firstName and lastName; any name in billingAddress is ignored. Latin letters are folded to ASCII (José and Yıldız become Jose and Yildiz), hyphens become spaces and apostrophes are dropped; a name that still cannot be used returns 422 cardholderNameInvalid (correct the customer’s name). An owning customer that is not an individual returns 422 accountTypeRequirementsNotMet.
Authorizations
Token from POST /auth/token
Headers
Required on POST and PUT requests. Use a unique value per logical mutation attempt, for example a UUID.
Body
Register a card as a customer-owned External Account, via the dedicated POST /external-accounts/cards endpoint. Cards are always customer-owned, so customerId is supplied directly here (no owner discriminator). The owner must be an individual customer, and the cardholder name is always that customer's firstName and lastName (a name the card issuer cannot accept fails with 422 cardholderNameInvalid). The card number and CVV live under details, using aliases in VGS-enabled environments or test digits in sandbox environments with VGS disabled. brand and cardType are derived server-side from the card number and are not accepted on the request.
Owning customer ID (cst_ prefix). Cards must be customer-owned.
Card number and CVV plus expiry (write-only); see CardCreateDetails for VGS-enabled and sandbox test-input requirements.
SDK risk session paired with deviceFingerprint. Required, 1–1024 printable non-space ASCII characters in every environment, including sandbox. Forwarded unchanged; write-only, never returned.
1 - 1024^[!-~]+$SDK device fingerprint paired with webSessionId. Required, 1–1024 printable non-space ASCII characters in every environment, including sandbox. Forwarded unchanged; write-only, never returned.
1 - 1024^[!-~]+$Card billing address (issuer AVS / fraud). Optional: when omitted, the owning customer's address is used (see billingAddressSource).
Response
The request has succeeded and a new resource has been created as a result.
A saved payment destination registered for a customer or one of their counterparties. Write-only secrets (full account number, full IBAN) are never present on reads, only their last-4 renderings.
The customer the account is registered for, also when one of their counterparties owns it.
The account's type-specific values.
External Account ID (ext_ prefix).
^[a-z]+_([0-9a-hjkmnp-tv-z]{26}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$The account type; decides which details keys are present.
bankUs, bankIban, bankCanada, card, walletExternal The counterparty that owns the account. Left out when the account is the customer's own.
usd on bank accounts and cards. Left out on wallets, which list their assets in supportedDestinations.
Where this account can be paid, computed per account. A US bank account lists the rails it can receive on, narrowed by what your project and the platform enable; an account Erebor Bank, N.A. provisions reports ach, achSameDay and wire, so a missing rtp there means unverified, not refused. A wallet lists usdc and usdt on each network its family serves. Left out for IBAN and Canadian bank accounts and cards.
US bank accounts: rails the project has turned on that this account cannot be paid over, each with its reason.
Current lifecycle status. A transition to invalid always fires the externalAccount.statusChanged webhook.
active, pending, rejected, invalid, deleted, failed Why the account failed, was rejected or became invalid. Present only when status is failed, rejected or invalid.
Optional display label.
Free-form key-value pairs supplied at creation or update.
When the external account was registered.
When the external account was last updated.