Field Validation Rules
Every regex the retail API enforces, and where the published rules differ from them.
Before You Start
Read the following guides before proceeding:
| Guide | Why |
|---|---|
| Api Basics | Request configuration |
Overview
The API validates most string fields against a regular expression before the request reaches a handler.
The patterns are published at runtime so a client can validate locally and avoid a round trip.
GET /api/v1/validation/rules
No user identity headers are required — see Authentication.
Response:
{
"rules": {
"firstName": "^[a-zA-Z][a-zA-Z\\-' ]{1,50}$",
"email": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"iban": "^[A-Z]{2}\\d{2}[A-Z0-9]{11,30}$"
}
}| Field | Description |
|---|---|
rules | Map of rule name to regular expression |
The published rules are not identical to the enforced ones. Two are stricter than the API, two
are published for fields the API does not validate, and four enforced patterns are not published at
all. The differences are listed below. Where they disagree, the enforced pattern is what decides
whether a request succeeds.
Enforced Patterns
These are the patterns the API applies.
Identity
| Rule | Pattern | Applies to |
|---|---|---|
| First name | ^[a-zA-Z][a-zA-Z\-' ]{1,50}$ | first_name |
| Last name | ^[a-zA-Z][a-zA-Z\-' ]{1,50}$ | last_name |
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ | email | |
| Phone | ^\+?\d{1,15}$ | phone_number on user creation, KYC data update and phone-number change |
| Company name | ^[A-Za-z0-9&.,'’\-/() :+#]{2,100}$ | company_name |
| Recipient name | ^[\p{L}\p{N}\s.'\-]{1,100}$ | recipient_name on corridor payment details |
Names accept only unaccented Latin letters, apostrophes, hyphens and spaces, and must be at least
2 characters — the pattern is a leading letter plus 1–50 more. Recipient names accept any Unicode
letter or digit, soJosé Mülleris a valid recipient name and an invalid user name. Transliterate
before sending.
Addresses
| Rule | Pattern | Applies to |
|---|---|---|
| Address line 1 | ^[A-Za-z0-9&.,'’\-/() :+#]{2,100}$ | line1 |
| Address line 2 | ^[A-Za-z0-9&.,'’\-/() :+#]{2,100}$ | line2 |
| City | ^[A-Za-z\s\-'.]{2,100}$ | city |
| State | ^[A-Za-z][A-Za-z\s\-']{1,99}$ | state |
| Postal code | ^[A-Za-z0-9\s\-]{3,12}$ | zip_code |
| Country | ^[A-Z]{2}$ | country |
City accepts no digits. An address in a city whose name contains a numeral fails validation.
zip_codeis always required on card delivery, including for countries with no postal code. Send
a placeholder that matches the pattern, e.g."00000"— only the format is validated. See
Card Issuance.
Blockchain
| Rule | Pattern | Applies to |
|---|---|---|
| Address | ^(0x[a-fA-F0-9]{40}|C[A-Za-z2-7]{55}|G[A-Za-z2-7]{55}|T[1-9A-HJ-NP-Za-km-z]{33}|r[1-9A-HJ-NP-Za-km-z]{24,34})$ | Wallet, token and account addresses |
| XRPL public key | ^[0-9A-Fa-f]{2,200}$ | user_public_key on POST /api/v1/accounts, and the action-confirmation token request |
The address pattern covers five chains in one alternation: EVM 0x, Stellar C and G, Tron T, and
XRPL r-addresses. See Ripple (XRPL).
Cards
| Rule | Pattern | Applies to |
|---|---|---|
| Card name | ^[a-zA-Z0-9\-':+# @]{1,50}$ | card_name on card issuance |
| Expiry date | ^(0[1-9]|1[012])/\d{4}$ | expiry_date on card activation |
| Card number last 4 | ^\d{4}$ | card_number_last4 |
expiry_dateis a four-digit year —01/2028, not01/28.
name_on_cardis accepted without any pattern check. It is bound on every card-issuance request
and never matched against a regex, so whatever you send goes to the card processor, which may reject
the order with a synchronous400. Do not rely on the publishednameOnCardrule — see below.
Bank Details
| Rule | Pattern | Applies to |
|---|---|---|
| IBAN | ^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$ | iban |
| UAE IBAN | ^AE\d{21}$ | iban on IPP details |
| BIC | ^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$ | bic |
| Account number | ^\d{8,18}$ | account_number on Faster Payments and ACH |
| Sort code | ^\d{6}$ | sort_code |
| Routing number | ^\d{9}$ | routing_number |
| CLABE | ^\d{18}$ | clabe |
| Currency | ^[A-Za-z0-9]{3,10}$ | currency on the recipient currency filter |
| Catalog index | ^[a-zA-Z]$ | catalog_index on the recipient catalog filter |
IBAN and card number checksums are not validated. Only the format is checked, so a format-valid
IBAN with a bad check digit passes here and is rejected by the rail after the transfer is accepted.
Cross-Chain Recipients
| Rule | Pattern | Applies to |
|---|---|---|
| Cross-chain address | ^[A-Za-z0-9:._\-]{1,128}$ | cross_chain_crypto.address |
| Cross-chain network | ^[A-Za-z0-9 \-_]{1,32}$ | cross_chain_crypto.network |
| Cross-chain tag | ^[A-Za-z0-9 \-_:.]{1,64}$ | cross_chain_crypto.tag |
These are deliberately permissive — a cross-chain destination is validated by the receiving network, not
by this API.
Corridor Details
| Rule | Pattern | Applies to |
|---|---|---|
| PIX key type | ^(CPF|CNPJ|EMAIL|PHONE|EVP)$ | pix_key_type |
| PIX key — CPF | ^\d{11}$ | pix_key |
| PIX key — CNPJ | ^\d{14}$ | pix_key |
| PIX key — phone | ^\+55\d{10,11}$ | pix_key |
| PIX key — EVP | ^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$ | pix_key |
| FPS ID type | ^(FPS_ID|MOBILE|EMAIL)$ | fps_id_type |
| FPS-HK bank code | ^\d{3}$ | bank_code on FPS-HK |
| IMPS account number | ^\d{9,18}$ | account_number on IMPS |
| IFSC code | ^[A-Z]{4}0[A-Z0-9]{6}$ | ifsc_code |
| InstaPay bank code | ^([A-Z]{3,4}|[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?)$ | bank_code on InstaPay |
| BI-FAST bank code | ^(\d{3}|[A-Z]{4}ID[A-Z]{2})$ | bank_code on BI-FAST |
| NIP account number | ^\d{10}$ | account_number on NIP |
| NIP bank code | ^\d{3}$ | bank_code on NIP |
| PSE account type | ^(SAVINGS|CHECKING)$ | account_type on PSE |
| PSE bank code | ^\d{3}$ | bank_code on PSE |
| PSE document type | ^(CC|NIT|CE|PP)$ | document_type on PSE |
| PSE document number | ^[A-Z0-9]{5,20}$ | document_number on PSE |
The PSE
document_typeregex acceptsPP, while the field's own documentation namesPASSPORT.
SendPP.
Savings
| Rule | Pattern | Applies to |
|---|---|---|
| Plan type | ^(Flexible|FixedTerms)$ | plan_type on savings account creation |
| Account name | ^[a-zA-Z0-9\-' ]{1,50}$ | name on savings account creation and rename |
| Term | ^\d+:\d+:\d+:\d+:\d+:\d+$ | term on savings account creation and upgrade |
See Earn.
Rates
| Rule | Pattern | Applies to |
|---|---|---|
| Tickers | ^[A-Za-z0-9]{2,10}/[A-Za-z0-9]{2,10}$ | tickers on GET /api/v1/rates |
Where the Published Rules Differ
| Rule | Published by GET /api/v1/validation/rules | Actually enforced | Consequence |
|---|---|---|---|
address | ^(0x…|C…|G…|T…)$ — four chains | Five chains, adding r[1-9A-HJ-NP-Za-km-z]{24,34} | The published rule rejects every XRPL address. A client validating locally against it cannot submit a valid XRPL account or recipient |
phoneNumber | ^\+\d{1,15}$ — leading + required | ^\+?\d{1,15}$ — leading + optional | The published rule rejects a bare 447700900123, which the API accepts |
nameOnCard | ^[A-Za-z][A-Za-z .'-]{1,25}$ | Not enforced | name_on_card is accepted unvalidated. Validating locally against the published rule rejects input the API would take — though the card processor may still refuse it |
cardNumber | ^\d{13,19}$ | Not used | No request validated by this rule accepts a full card number |
Four enforced patterns are not published at all — validate these from the table above rather than
from the endpoint:
crossChainAddress · crossChainNetwork · crossChainTag · xrplPublicKey
One naming quirk: the CLABE rule is published under the key
clabel, notclabe. A client
looking the rule up by name will miss it.
Error Shape
Validation failures come back in two shapes, depending on the endpoint.
Structured — the field is in error_details under the key field, usually with the offending
pattern:
{
"error_reason": "ErrorInvalidField",
"error_description": "Invalid format for card name",
"error_category": {
"category": "CategoryValidationFailure",
"http_status_code": 400
},
"error_details": [
{ "key": "field", "details": "card_name" },
{ "key": "issue", "details": "invalid_format" },
{ "key": "pattern", "details": "^[a-zA-Z0-9\\-':+# @]{1,50}$" }
]
}Keyed — the field is the key, and error_description is generic:
{
"error_reason": "ErrorGeneral",
"error_description": "Request failed validation",
"error_category": {
"category": "CategoryValidationFailure",
"http_status_code": 400
},
"error_details": [
{ "key": "first_name", "details": "First name is invalid" }
]
}Handle both: read error_details[].key, and where it is field, read the field name from details.
The structured shape carries the offending pattern, which is the most reliable source of the enforced
regex for a specific field — more reliable than the published rules map.
Updated 20 days ago

