Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.wellapp.ai/llms.txt

Use this file to discover all available pages before exploring further.

ExchangeRate represents a daily FX conversion rate between two ISO 4217 currencies for a given date. It is used primarily by the multi-currency accounting pipeline: invoices and invoice-transactions reference an ExchangeRate row via a FK to convert document-currency amounts into the workspace’s accounting base currency. The workspace relation is nullable, allowing for global market-rate rows that are not scoped to a specific tenant, while workspace-specific overrides carry a workspace FK. It is a Supporting entity in the records graph, surfaced as the exchange_rates root and referenced as a relation from invoices and invoice_transactions.
NamingValue
ObjectExchange Rate
Resource type (JSON:API type)exchange_rate
Collection / records rootexchange_rates
REST base/v1/exchange-rates
Entity classExchangeRate

API operations

OperationMethod & pathStatus
ListGET /v1/exchange-rates✅ Implemented
RetrieveGET /v1/exchange-rates/{id}✅ Implemented
CreatePOST /v1/exchange-rates🟡 Planned
UpdatePATCH /v1/exchange-rates/{id}🟡 Planned
DeleteDELETE /v1/exchange-rates/{id}🟡 Planned

Data model

Attributes

FieldTypeRequiredConstraintsAllowed valuesDescription
exchange_rate_idstring, UUID✅ Yesunique; default gen_random_uuid()Public stable identifier for this exchange rate row. Generated server-side via gen_random_uuid(); never set by the client.
source_currencystring (CurrencyCodeEnum)✅ YesNOT NULL; must differ from target_currency (CHECK source_currency != target_currency); maps to Postgres native enum currency_code_enumISO 4217 codes: USD, EUR, GBP, JPY, CHF, CAD, AUD, NZD, SEK, NOK, DKK, PLN, CZK, HUF, RON, BGN, … (full CurrencyCodeEnum, ~180+ values)The currency being converted from. Together with target_currency and rate_date, forms part of the composite uniqueness key (scoped to workspace).
target_currencystring (CurrencyCodeEnum)✅ YesNOT NULL; must differ from source_currency (CHECK source_currency != target_currency); maps to Postgres native enum currency_code_enumISO 4217 codes: USD, EUR, GBP, JPY, CHF, CAD, AUD, NZD, SEK, NOK, DKK, PLN, CZK, HUF, RON, BGN, … (full CurrencyCodeEnum, ~180+ values)The currency being converted to. The rate expresses how many target_currency units equal one source_currency unit.
ratestring (decimal, 18,8 precision)⚪ Nonullable; DECIMAL(18,8); CHECK rate > 0 (enforced in migration DDL)The conversion rate from source_currency to target_currency on rate_date. Stored as a decimal string to preserve full precision. A null value indicates the rate was recorded but the numeric value is not yet resolved.
rate_dateDate (date column)✅ YesNOT NULL; columnType date (date-only, no time component); part of the composite uniqueness key (workspace, source_currency, target_currency, rate_date)The calendar date for which this rate is valid. One row per (workspace, source_currency, target_currency, date) tuple.
sourcestring⚪ Nonullable; length 100Free-text provenance label for the rate — e.g. the feed name, connector slug, or manual entry identifier. Used to distinguish ECB daily rates from connector-sourced or user-overridden rates.
created_atstring (ISO 8601 datetime), 🔒 system✅ Yesset by MikroORM onCreate lifecycle hook; never writable after creationTimestamp when the row was first persisted. Set server-side only.
updated_atstring (ISO 8601 datetime), 🔒 system⚪ Noset by MikroORM onCreate and onUpdate lifecycle hooksTimestamp of the last mutation to this row. Auto-maintained by the ORM.
deleted_atstring (ISO 8601 datetime) | null⚪ Nonullable; soft-delete sentinel; queries must filter deleted_at IS NULLSoft-delete timestamp. Non-null means this rate has been logically removed. All standard queries must include the deleted_at IS NULL predicate.

Relationships

NameTypeRequiredDescription
workspaceto-one (workspace)⚪ No (nullable)The Workspace that owns this exchange rate row. Nullable: a null workspace_pk indicates a global market-rate record not scoped to a specific tenant. When set, combined with source_currency, target_currency, and rate_date forms the unique constraint. FK: exchange_rates.workspace_pk → workspaces.pk. Index: idx_exchange_rates_workspace_date on (workspace_pk, rate_date).

System-computed

  • exchange_rate_id is generated server-side via gen_random_uuid() as the Postgres column default, with a JavaScript fallback randomUUID() set at entity construction time in the MikroORM entity. Never supplied by the client.
  • created_at is set by the MikroORM onCreate lifecycle hook (new Date()) and never subsequently updated.
  • updated_at is set by both onCreate and onUpdate lifecycle hooks, reflecting the timestamp of the most recent mutation.
  • deleted_at is null by default. Soft-delete is applied by setting this field to a non-null timestamp; hard deletes are not used. All read queries must filter deleted_at IS NULL.
  • Composite uniqueness constraint: UNIQUE(workspace_pk, source_currency, target_currency, rate_date) — enforced at the database level. This makes the table an upsert target: the FX pipeline can safely attempt INSERT … ON CONFLICT (workspace_pk, source_currency, target_currency, rate_date) DO UPDATE.
  • rate carries a database-level CHECK (rate > 0) constraint added in Migration20260306100000 DDL, even though the column is nullable — when rate is non-null, it must be strictly positive.
  • The CHECK (source_currency != target_currency) constraint is enforced at the database level (Migration20260306100000). The MikroORM entity does not replicate this at the application layer; it is a DB-only guard.
  • The workspace relation is nullable (nullable: true on the @ManyToOne decorator), allowing global rates to exist without a workspace owner. Workspace-scoped rates take precedence over global rates in the FX resolution service.
  • Index idx_exchange_rates_workspace_date on (workspace_pk, rate_date) supports the FX rate lookup pattern: given a workspace and a date, resolve the applicable rate for a currency pair.
  • Invoices reference ExchangeRate via invoices.exchange_rate_pk FK (added in Migration20260306100000). InvoiceTransaction rows reference ExchangeRate via invoice_transactions.exchange_rate_pk FK (added in Migration20260311100000_data_model_v2_accounting). ExchangeRate rows are pipeline-written; they are not created through the standard REST API by end users.
  • The composites.yml file defines two composites for the exchange_rates root: composite_invoices_list (relation_list of linked invoices) and composite_invoice_transactions_list (relation_list of linked invoice_transactions). These are read-only aggregate views surfaced in the Records table.
  • The workspace relation on exchange_rates is not guarded by the standard workspace-scoped Hasura RLS filter because the nullable workspace_pk means global rows have no workspace. This is an intentional architectural exception documented in the entity design.

Example

{
  "data": {
    "type": "exchange_rate",
    "id": "c3e1b2f4-09d7-4a8e-b5e0-7f2a1d3c6890",
    "attributes": {
      "exchange_rate_id": "c3e1b2f4-09d7-4a8e-b5e0-7f2a1d3c6890",
      "source_currency": "USD",
      "target_currency": "EUR",
      "rate": "0.92150000",
      "rate_date": "2026-05-15",
      "source": "ecb-daily-feed",
      "created_at": "2026-05-15T08:00:14.000Z",
      "updated_at": "2026-05-15T08:00:14.000Z",
      "deleted_at": null
    },
    "relationships": {
      "workspace": {
        "data": {
          "type": "workspace",
          "id": "a1b2c3d4-1111-2222-3333-444455556666"
        }
      }
    }
  }
}
Source: apps/api/src/database/entities/ExchangeRate.ts · domain: financial-graph · tier: Supporting