Skip to main content
Human-facing surfaces of the platform (hosted pages, PDFs, CSV headers) resolve a locale of 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.
Register (POST /v1/auth/register) and admin-created accounts stamp locale at birth: explicit body locale > Accept-Language > org default_locale
English. Invalid non-empty locale on register is the same 400 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-Language or the org default says otherwise.

Organization default

Platform admins set default_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-Languageen. 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

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.
No. JSON bodies and webhooks stay in English. Locale applies to hosted HTML, PDFs, CSV headers and similar human documents.
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.
Last modified on August 18, 2026