en, es or zh. The default is English. JSON API
responses and webhooks stay in English regardless of locale (labels, error
codes and message strings).
locale on the account is the human preference. It never translates the
JSON contract. If you need a Spanish PDF, pass ?lang=es or set the
account locale — the GET /v1/payouts/{id} body is still English.Resolution chain
Authenticated API calls (resolveRequestLocale) walk this list and stop at
the first valid value. An invalid ?lang= / ?locale= is ignored
(never 400) and the next step wins.
Public pages (checkout, tracker, receipts, status, hosted card page) insert
one extra step between the query and the account: the payer cookie
cbpay_pay_locale (see Cookies).
Workers, receipt emails and PDFs generated without a request use the account
locale, then the org default, then English.
Set the account locale
GET /v1/me exposes locale (en | es | zh). Missing or invalid
stored values are returned as en.
PATCH /v1/me accepts locale (string). It stays editable after KYC/KYB
approval — unlike display_name, tax_id and country.
POST /v1/auth/register) and admin-created accounts stamp locale
at birth: explicit body locale > Accept-Language > org default_locale
English. Invalid non-emptylocaleon register is the same400 invalid_locale.
Existing accounts vs new accounts
A one-shot deploy migration (db/platform/092_account_locale_es_preconfig.sql)
sets locale=es on accounts that had no locale yet. It is not an
API. After that deploy:
- Existing accounts stay in Spanish until the holder patches
locale. - New accounts are born in English unless the body,
Accept-Languageor the org default says otherwise.
Organization default
Platform admins setdefault_locale with
PUT /v1/admin/orgs/{orgID}/settings (key: default_locale, value "en",
"es" or "zh"). "" clears the override so the org falls back to
English. Invalid values return 400 invalid_value (not invalid_locale).
The cbpay organization does not carry this setting — accounts there use
the rest of the chain.
Public pages, checkout and tracker
Hosted pages honor, in order:?lang= / ?locale= → cookie
cbpay_pay_locale → (if there is a session) account locale → org default →
Accept-Language → en. The HTML is emitted with <html lang="...">.
Query overrides are never a 400. Unknown values fall through.
Receipt and statement PDFs accept ?lang=en|es|zh (default English).
Filenames follow the locale: statement_… / receipt_… (en),
cartola_… / comprobante_… (es), 对账单_… / 收据_… (zh).
Human HTTP responses stamp Content-Language and Vary: Accept-Language.
Two cookies
Do not send a
cb_locale cookie — it is not part of this API.
CSV exports
Platform CSV downloads (movements, payouts, payins, transfers, revenue, audit log, Qscore batch results, card investigations, firewall export) localize header row labels to the caller’s locale. Cell values stay raw (IDs, amounts, status codes). Expense CSV is unchanged in this release. Example Qscore batch header in English:Out of scope
Telegram notifications, the card issuer’s 3-D Secure challenge page, and right-to-left scripts are not localized by this chain.Errors
Full catalog: Errors. Profile fields: Your profile.
FAQ
Why is my existing account in Spanish after this release?
Why is my existing account in Spanish after this release?
Accounts that already existed were pre-configured
locale=es by the
one-shot deploy migration. New accounts default to English. Patch
locale on PATCH /v1/me to switch.Does changing locale translate API JSON?
Does changing locale translate API JSON?
No. JSON bodies and webhooks stay in English. Locale applies to hosted
HTML, PDFs, CSV headers and similar human documents.
Why did an invalid ?lang=es-MX not return 400?
Why did an invalid ?lang=es-MX not return 400?
Query locale is best-effort. Unsupported values are ignored and the next
step in the chain is used. Only
PATCH /v1/me / register persist a
locale and reject garbage with invalid_locale.