CorebanqCorebanq Developer Docs
Currencies

Description

Purpose and use

Currencies define which fiat and crypto denominations the bank can hold, display, and validate, and the precision rules that govern how amounts in those denominations are stored and shown. This module is the catalog — the reference list every account balance, transfer amount, tariff, and ledger posting checks against. It does not hold exchange rates: FX pricing and conversion live in the FX module.

Who uses this. Treasury, operations, product managers, finance controllers, and support consult currency records when activating a new money type or explaining amount precision. Maintaining the catalog is an administrative action gated by role.

How it works. Each currency carries its identity (code, numeric code, name, symbol), display metadata (format, icon, priority), activity status, and precision rules. Those precision rules determine how a major-unit amount shown to a user maps to the minor-unit value used for calculation and posting.

What users do. Review the active currencies, check a currency's decimal precision, add or update a currency, activate or deactivate one, and confirm that a product, account, or transfer uses an allowed denomination.

Outcomes and side effects. Currency changes affect validation, amount formatting, portfolio totals, and tariff calculation. They do not move money, but they can allow or block new account and transfer activity. All operations are gated by role-based access on the currencies record type and written to the audit trail.

Related manuals: FX, Accounts & Portfolios, Transfers, Tariffs.

The currency record

FieldMeaning
CodeThree-letter ISO 4217 or asset code (e.g. EUR, BTC)
Numeric codeISO 4217 numeric code
Name, symbolDisplay identity
Decimal placesDecimals shown to users
PrecisionInternal calculation precision (minor units)
RoundingRounding rule — up, down, or half_up
Fiat / baseWhether the currency is fiat, and whether it is the base for FX calculations
Format, icon, display priorityPresentation controls; the icon URL is resolved on read from configuration
ActiveWhether the currency is available for use
Audit fieldsCreated and last-modified user and timestamp

Tasks

List currencies

GET /v1/currencies

GET /v2/currencies

Two listings exist:

  • v1 returns a plain list of the currencies the caller may see, and accepts simple field filters (code, numeric code, symbol, decimal places, precision, rounding, format, name, description, display priority, fiat/base flags, active), plus a limit and offset. Unknown filter fields are ignored rather than rejected.
  • v2 is the paginated, search-oriented listing. It accepts structured search fields, sorting, and paging, and returns a result envelope carrying the page of records, the total, the unfiltered total, and whether more pages remain. When crypto inclusion is requested, it also joins linked crypto accounts and validates the supplied project (and optional customer) scope. Use v2 for operational grids and search screens. limit=-1 skips data fetching and returns only the count (total/total_unfiltered); any other value <= 0 falls back to the default limit.

Results are always scoped by role — a caller sees only the currencies their permissions allow.

View one currency

GET /v1/currencies/{id}

Returns the full record for a single currency, with its icon URL resolved. The identifier must be a valid currency reference.

Add a currency

POST /v1/currencies

Creates a new currency from its code, name, symbol, precision, and display metadata, and returns the created record. Adding a currency requires create permission on the currencies catalog. A currency name must be unique — attempting to reuse an existing name is rejected as a server-side conflict; confirm the name is free before adding.

Update a currency

PUT /v1/currencies/{id}

Replaces the editable fields of an existing currency from the supplied values and returns the updated record. Fields omitted from the request are not preserved automatically — send the complete intended state. Requires update permission.

Activate or deactivate a currency

There is no separate toggle endpoint — set the active flag through the update operation. Deactivating a currency keeps the record but removes it from the denominations available for new activity; use this rather than deletion when a currency should stop being offered but its history must remain.

Delete a currency

DELETE /v1/currencies/{id}

Permanently removes the currency record. This is a hard removal, not a deactivation — the row is gone. Requires delete permission. Because accounts, balances, tariffs, and postings reference currencies, prefer deactivating (setting active off) over deleting unless the currency was created in error and is unused. On success the operation returns a standard confirmation message.

Amount and precision handling

This section explains why currency precision matters across the platform; the rules are owned here and consumed everywhere amounts are handled.

All monetary amounts in the system are represented as integer values in minor (atomic) units, carried as strings. Floating-point numbers are never used for money.

LayerFormat
Ledger / APIAtomic integers
User interfaceFormatted decimals

Why. Integer atomic units prevent rounding drift and settlement mismatches, keep accounting deterministic and easy to reconcile, and satisfy the regulatory expectation of traceable, reversible calculations.

Internal representation

AssetInternal unitPrecision
EUR / USDcents2
BTCsatoshis8
ETHwei18
USDCmicro6

How precision travels with an amount

  • An amount is always accompanied by its currency, so the minor-unit value is never ambiguous.
  • On reads, the API also returns the precision — the number of decimals the minor unit represents — so every returned amount is fully self-describing.
  • On writes, callers send only the amount and currency; precision is not accepted in requests — the server resolves it from the currency record. This is why the currency catalog is the single source of precision truth.

Common pitfalls

  • Precision mismatch. Crypto assets support up to 18 decimals while fiat typically uses 2. Always convert through atomic units; the explicit precision on reads removes ambiguity.
  • Conversion loss. Avoid chained conversions (e.g. BTC → EUR → USD). Convert atomic ledger value → base currency → quote currency. Exchange-rate handling itself lives in the FX module.
  • Regulatory expectations. Auditors expect deterministic rounding, traceable calculations, and reversible accounting — integer atomic units satisfy all three.

Common failures and what they mean

SituationWhat the operator seesWhat to do
Not signed inAuthorization failureSign in with a valid session
No permission on the catalogAccess deniedConfirm the role grants the needed currencies permission
Currency not foundNot foundVerify the currency reference
Duplicate currency name on addRejected as a server errorUse a name not already in the catalog
Malformed request bodyInvalid inputCorrect the payload and retry

Underlying message keys (for support triage) live in the module's localization catalogue; operators act on the business meaning above.

GETList countries (v2)

Returns countries in the standard paginated envelope through the shared query parser. Filterable and sortable fields: id, iso_alpha_2, iso_alpha_3, iso_numeric, icon, emoji, phone_prefix, order, iban_length, name, description, tags, created_at, created_by, modified_at, modified_by, active. Free-text search covers iso_alpha_2, iso_alpha_3, iso_numeric, icon, emoji, phone_prefix, name and description. Two fields are deliberately NOT listed. metadata is not in validFieldsV2 at all, so querying it answers 500 query_m.invalid_search_field — NOT a 400, and not query_m.invalid_field. validateFieldName builds MsgInvalidSearchField with no WithCode, and an empty Code sends HandleAppErrorWithCode to 500. Do not confuse it with query_m.invalid_field: that one has two unrelated sources, both answering 400 — validateFieldName raises it when the name fails the character regex, and the stack parser raises it for an unknown stack field. An unknown SORT field is query_m.invalid_sort_field, 400. icon_url IS in validFieldsV2 but has no column behind it (models.Country marks it gorm:"-"), so filtering or sorting on it builds SQL referencing a column that does not exist and returns 500 — a pre-existing defect in validFieldsV2, not something to rely on. Sorting on it is subject to the same data-pass caveat as any other sort key: see the sort parameter. NO RECORD-LEVEL PERMISSION FILTER APPLIES. The service passes UserID: uuid.Nil (its own comment calls this a guest route), and the shared getAll path short-circuits on uuid.Nil before the permitted-records lookup, merging a nil scope. So any caller with a valid token receives every row and unfiltered total/total_unfiltered counts, regardless of record grants. RbacRecordType is passed but unused on this path. The v1 list has no record scoping either.

GETList currencies (v1)

Handler: GetAllCurrencies → SrvGetAllCurrencies. Returns a **bare array** of models.Currency (NOT a get_all envelope). Results are RBAC-scoped (PermReadAll or permitted record IDs). Query uses **flat** field names passed to ConstructQuery + CurrencyQueryParser — invalid fields are silently ignored. Use GET /v2/currencies for paginated get_all + search.* syntax.

On this page