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.

A Transaction represents a single monetary movement recorded against a bank account — a debit or credit of funds initiated by a connector sync (e.g. Plaid, Qonto/PSD2) or created manually. Every transaction belongs to a Workspace and is anchored to an AccountBalance; the two legs of the movement are modelled as a debtor PaymentMeans (source of funds) and a creditor PaymentMeans (destination). Transactions are the primary evidence surface for counterparty-bank discovery, AI categorisation, invoice reconciliation, and the accounting journal-entry pipeline.
NamingValue
ObjectTransaction
Resource type (JSON:API type)transaction
Collection / records roottransactions
REST base/v1/transactions
Entity classTransaction

API operations

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

Data model

Attributes

FieldTypeRequiredConstraintsAllowed valuesDescription
transaction_idstring, UUID, 🔒 system✅ Yesunique; generated via gen_random_uuid()Public immutable identifier for the transaction. Exposed on all API responses; used for deduplication and external references.
typestring (text, CHECK constraint)⚪ Nonullable; TEXT column with CHECK IN (…) — not a native PG enum. MikroORM @Enum without nativeEnumName stores the enum VALUE string.General payments to vendors or suppliers, Transfers between accounts, Incoming funds or deposits, Cash withdrawals or outgoing funds, Credit/debit card transactions, Automated recurring payments, Refunds or previous paid funds, Services fees and charges, Interest earned or charged, Miscellaneous or unclassified transactionHigh-level classification of the transaction’s economic nature. The stored DB string is the full description value from TransactionTypeEnum (e.g. ‘General payments to vendors or suppliers’ for PAYMENT). Written by connector sync at ingest time.
statusstring (text, CHECK constraint)⚪ Nonullable; TEXT column with CHECK IN (…) — not a native PG enum. MikroORM @Enum without nativeEnumName stores the enum VALUE string.Initiated, awaiting processing, Processing in progress, Authorized but not yet settled, Successfully completed and settled, Failed due to technical errors, Rejected by the recipient or system, Cancelled by the initiator or system, Reversed or rolled back, Held for review, Expired without completionLifecycle stage of the transaction. The stored DB string is the full description value from TransactionStatusEnum (e.g. ‘Successfully completed and settled’ for COMPLETED). Terminal success: ‘Successfully completed and settled’. Terminal failures: Failed/Rejected/Cancelled/Reversed descriptions. Transient: Initiated/Processing/Authorized descriptions.
transaction_external_idstring⚪ Nonullable; length ≤ 255; indexed (idx_transactions_external_id) for connector sync batch dedupProvider-assigned identifier (e.g. Plaid transaction_id, Qonto entry id). Used by connector sync to resolve existing rows via IN (…) dedup query before creating new ones.
requested_execution_datedate⚪ Nonullable; stored as PostgreSQL DATE (no time component)The date the initiator requested the transaction to be executed. May differ from executed_at when the bank settles on a different day than the request.
executed_attimestamp✅ YesNOT NULL; indexed (idx_transactions_executed_status, idx_transactions_workspace_executed_active)Timestamp when the transaction was executed by the banking system. Primary time axis for canvas burn-window reads and temporal ordering. Indexed with workspace_pk (partial WHERE deleted_at IS NULL) for anchor-most-recent and range-window query patterns.
booking_datedate⚪ Nonullable; stored as PostgreSQL DATEDate the transaction was booked in the account ledger at the bank. Used in PSD2/Open Banking flows; may lag executed_at by one banking day.
value_datedate⚪ Nonullable; stored as PostgreSQL DATEDate on which the funds become available (interest calculation date). Used in counterparty-bank discovery recency decay and micro-deposit fingerprint detection (Q12 window: value_date BETWEEN t1.value_date AND t1.value_date + INTERVAL ‘14 days’).
instructed_amountobject (JSONB) — { amount: number; currency: CurrencyCodeEnum }✅ YesNOT NULL; JSONBThe amount and currency as instructed by the initiator. Negative values represent debits from the workspace perspective; positive represent credits. currency is an ISO-4217 code. This is the canonical amount column used for ABS() aggregation in counterparty-bank scoring (Q8 threshold, Q12 micro_amount).
settlement_amountobject (JSONB) — { amount: number; currency: CurrencyCodeEnum }⚪ Nonullable; JSONBThe amount actually settled, which may differ from instructed_amount when FX conversion occurs. Present for cross-currency transactions where the instructed currency differs from the account’s base currency.
foreign_exchangeobject (JSONB) — { rate: number; pair: string; source: CurrencyRateSourceEnum; at: Date }⚪ Nonullable; JSONBsource: ECB, FED, IMF, XE, OANDA, BANK, EXCHANGE_RATE_API, MANUAL, OTHERFX rate data applied to convert instructed_amount into settlement_amount. pair is an ISO currency pair string (e.g. EUR/USD). at is the timestamp of the rate snapshot. source identifies the rate provider.
category_purposestring⚪ Nonullable; length ≤ 10ISO 20022 category purpose code identifying the high-level purpose of the credit transfer (e.g. GDDS = goods and services, SUPP = supplier payment, SALA = salary). At most 10 characters per the ISO standard.
purpose_codestring⚪ Nonullable; length ≤ 10ISO 20022 purpose code providing additional detail beyond category_purpose (e.g. SUPP = supplier payment, SALA = salary, RENT = rental payment). At most 10 characters.
category_normalizedstring (text)⚪ Nonullable; DB CHECK length ∈ [1, 200] (transactions_category_normalized_length); DB CHECK category_source = ‘classifier’ ⟺ category_confidence IS NOT NULL (transactions_category_source_confidence_invariant); DB CHECK category_normalized IS NULL OR category_source IS NOT NULL (transactions_category_normalized_provenance)Human-readable normalized spending category (e.g. ‘Office Supplies’, ‘Travel & Accommodation’). Written by the W19 AI classifier (source = classifier), by explicit human override via POST /v1/workspaces/:wsId/transactions/:tId/category (source = user), by connector import (source = connector), or by a deterministic FieldRule (source = rule). The service layer enforces source = user on override writes, preventing provenance forgery.
category_confidencestring (decimal 4,3)⚪ Nonullable; DB CHECK ∈ [0, 1] (transactions_category_confidence_range); must be non-null iff category_source = ‘classifier’; forced to NULL on user override writesClassifier confidence score in [0.000, 1.000] for the assigned category_normalized value. Present only when category_source = classifier; NULL for user, connector, and rule sources. Stored as decimal(4,3) to avoid float precision drift.
category_sourcestring (enum CategorySourceEnum)⚪ Nonullable; native PG enum category_source_enum; invariant: classifier ⟺ category_confidence IS NOT NULLclassifier, user, connector, ruleProvenance of the category_normalized value. classifier = W19 AI model (carries confidence). user = explicit human override. connector = value imported verbatim from connector mapping (e.g. Plaid personal_finance_category). rule = deterministic FieldRule (no LLM, no confidence).
remittanceobject (JSONB) — { unstructured?: string; structured_reference?: string; reference_type?: RemittanceReferenceTypeEnum }⚪ Nonullable; JSONB; the unstructured field is the primary bank-discovery surface for PSD2/Qonto flowsreference_type: SCOR, QRR, ISR, IREF, EREF, PREF, MREF, CRED, USTD, NONPayment reference/remittance information. unstructured holds the free-text memo (primary BIC, IBAN, and bank-name extraction surface for PSD2 sources). structured_reference is a ISO 11649 creditor reference or similar. reference_type classifies the structured reference scheme. Accessed as remittance->>‘unstructured’ in SQL.
feesarray (JSONB) — Array<{ type: TransactionFeeTypeEnum; amount: number; currency: CurrencyCodeEnum }>⚪ Nonullable; JSONB arraytype stored strings: Standard Transfer fee, Wire Transfer or inter-bank transfer fee, Foreign Exchange conversion fee, ATM withdrawal or usage fee, Overdraft or insufficient funds fee, Monthly account maintenance fee, Card insurance, renewal or annual fee, Commission or percentage base fee, Late payment or violation penality, Miscellaneous or unclassified feeBreakdown of fees associated with this transaction. Each entry carries the fee type (stored as the TransactionFeeTypeEnum VALUE string), absolute amount, and currency. Multiple fee entries are possible (e.g. a wire transfer may carry both a transfer and a currency conversion fee). Note: ‘penality’ is spelled as in the code/enum.
schemestring (enum TransactionSchemeEnum)⚪ Nonullable; native PG enum transaction_scheme_enumSEPA, SWIFT, ACH, FASTER_PAYMENTS, BACS, WIRE, OTHERPayment rail / clearing scheme used to execute the transaction. SEPA = Eurozone credit transfer or direct debit. SWIFT = international correspondent banking. ACH = US domestic network (relevant for micro-deposit fingerprint Q12). FASTER_PAYMENTS = UK instant. BACS = UK direct debit. WIRE = generic bank wire. This column uses a native PG enum (nativeEnumName: transaction_scheme_enum) so the stored values are the enum keys.
raw_dataobject (JSONB) — Record<string, unknown>⚪ Nonullable; JSONB; no GIN index — queried via full-document ILIKE/::text cast in counterparty-bank discovery queries Q7/Q10/Q11Connector-native payload preserved verbatim. Shape varies per connector. For Plaid/Mercury: includes counterparties[] (name, type, confidence_level, website, logo_url, entity_id), merchant_name, merchant_entity_id, personal_finance_category, payment_meta (ppd_id, by_order_of), payment_channel, transaction_code. For PSD2/Qonto: minimal; bank identity surfaces in remittance.unstructured instead. For GoCardless/Tink: raw_data->‘institution’->>‘name’. Always branch on originating connector before parsing.
created_attimestamp, 🔒 system✅ YesNOT NULL; set by onCreate lifecycle hookTimestamp when the transaction row was first persisted in Well. Distinct from executed_at (bank execution time). Set automatically by MikroORM onCreate; never writable by the API.
updated_attimestamp, 🔒 system⚪ Nonullable; set by onCreate and onUpdate lifecycle hooksTimestamp of the most recent update to this row (category assignment, enrichment, soft-delete). Set automatically by MikroORM on every flush.
deleted_attimestamp⚪ Nonullable; soft-delete sentinel; all active queries must filter deleted_at IS NULL; partial indexes use WHERE deleted_at IS NULLSoft-delete timestamp. NULL means the record is active. Set to current timestamp on deletion; never physically removed. Partial indexes (idx_transactions_account_balance_active, idx_transactions_workspace_executed_active, idx_transactions_classifier_confidence) exclude deleted rows to avoid index bloat.

Relationships

NameTypeRequiredDescription
workspaceto-one (Workspace)⚪ No (nullable FK)The tenant workspace that owns this transaction. All active-record queries filter by workspace_pk. Indexed via idx_transactions_workspace_deleted (composite workspace_pk + deleted_at) for Hasura RLS permission filter.
debtor_payment_meansto-one (PaymentMeans)⚪ No (nullable FK, fieldName: debtor_payment_means_pk)The payment instrument on the source-of-funds (debit) side of the transaction. When company_pk on this PaymentMeans equals the workspace’s own_company_pk the transaction is an outbound payment from the workspace. Used in counterparty-bank direction-of-ownership analysis.
creditor_payment_meansto-one (PaymentMeans)⚪ No (nullable FK, fieldName: creditor_payment_means_pk)The payment instrument on the destination-of-funds (credit) side of the transaction. When company_pk on this PaymentMeans equals own_company_pk the transaction is an inbound receipt to the workspace.
account_balanceto-one (AccountBalance)⚪ No (nullable FK, fieldName: account_balance_pk)The account balance snapshot associated with this transaction. Provides the link to the parent Account (accounts.pk via account_balances.account_pk). Used in cash-flow canvas burn-window reads and by the soft-delete partial index idx_transactions_account_balance_active.
sourceWorkspaceConnectorto-one (WorkspaceConnector)⚪ No (nullable FK, entity property name: sourceWorkspaceConnector — camelCase)The connector sync instance that created this transaction row. NULL means the transaction was created via a non-connector path (manual entry, invoice import). Non-NULL identifies the ingestion provenance (Plaid, Qonto, GoCardless, etc.) and enables connector-source branching in raw_data parsing.
ledger_accountto-one (LedgerAccount)⚪ No (nullable FK)The accounting ledger account to which this transaction is classified (e.g. chart-of-accounts code 512 – Bank). Set during the accounting journal-entry pipeline. NULL until the transaction is posted.
transaction_documentsto-many (TransactionDocument)Documents attached to this transaction (receipts, proofs of payment, bank statements). Managed via TransactionDocument pivot; each document carries a reference back to this transaction.
transactionWorkspaceConnectorsto-many (TransactionWorkspaceConnector)Multi-connector provenance pivot linking this transaction to one or more WorkspaceConnector instances that have touched it (e.g. a transaction first ingested by Plaid, later enriched by a second connector). Entity property name is camelCase transactionWorkspaceConnectors in the MikroORM entity.

System-computed

  • transaction_id: generated via gen_random_uuid() PostgreSQL function at INSERT time; unique constraint enforced at DB level.
  • created_at: set by MikroORM onCreate lifecycle hook (new Date()); never writable via API.
  • updated_at: set by both onCreate and onUpdate lifecycle hooks; reflects latest flush timestamp.
  • deleted_at: soft-delete sentinel; NULL on active records. Set by service layer on deletion; never physically removed. All active queries must carry a deleted_at IS NULL predicate.
  • category_source invariant: when category_source is set to ‘user’ the service layer forces category_confidence to NULL, regardless of payload content, to prevent provenance forgery via Hasura or direct API calls.
  • category_confidence invariant: DB CHECK (transactions_category_confidence_range) enforces category_confidence ∈ [0, 1]; DB CHECK (transactions_category_source_confidence_invariant) enforces that category_confidence IS NOT NULL if and only if category_source = ‘classifier’.
  • category_normalized invariant: DB CHECK (transactions_category_normalized_length) enforces length(category_normalized) ∈ [1, 200] when non-null; DB CHECK (transactions_category_normalized_provenance) enforces category_normalized IS NULL OR category_source IS NOT NULL — a non-null label always requires a non-null source.
  • transaction_external_id dedup: connector sync resolves existing rows via transaction_external_id IN (…) batch lookup (idx_transactions_external_id index) before creating new ones.
  • sourceWorkspaceConnector provenance: non-null value records which WorkspaceConnector created this row; NULL indicates manual/non-connector origin. The raw_data parsing strategy must branch on the originating connector before reading connector-specific fields.
  • instructed_amount is the canonical amount column: used for ABS() aggregation in counterparty-bank scoring queries, micro-deposit fingerprint detection, and large-transaction detection (Q8/Q12). Settlement amount is the FX-converted equivalent and is always in a different or equal currency.
  • Partial indexes: idx_transactions_account_balance_active (WHERE deleted_at IS NULL AND account_balance_pk IS NOT NULL); idx_transactions_workspace_executed_active (WHERE deleted_at IS NULL); idx_transactions_classifier_confidence (WHERE category_source = ‘classifier’ AND deleted_at IS NULL, on workspace_pk + category_confidence — serves the classifier review queue). All exclude deleted rows from hot-path index scans.
  • type and status enum storage: these columns are TEXT with CHECK IN (…) constraints (not native PG enums). MikroORM @Enum without nativeEnumName stores the enum VALUE string (long description), not the enum key. When filtering in SQL, use the description string, e.g. WHERE type = ‘General payments to vendors or suppliers’, not WHERE type = ‘PAYMENT’.

Example

{
  "data": {
    "type": "transaction",
    "id": "d3f4a8b2-1c9e-4d7f-b6a0-2e5c8f901234",
    "attributes": {
      "transaction_id": "d3f4a8b2-1c9e-4d7f-b6a0-2e5c8f901234",
      "type": "General payments to vendors or suppliers",
      "status": "Successfully completed and settled",
      "transaction_external_id": "txn_1OqwXY2eZvKYlo2CABCdef12",
      "requested_execution_date": "2026-05-14",
      "executed_at": "2026-05-14T09:32:00.000Z",
      "booking_date": "2026-05-14",
      "value_date": "2026-05-15",
      "instructed_amount": {
        "amount": -1250.00,
        "currency": "EUR"
      },
      "settlement_amount": {
        "amount": -1250.00,
        "currency": "EUR"
      },
      "foreign_exchange": null,
      "category_purpose": "GDDS",
      "purpose_code": "SUPP",
      "category_normalized": "Office Supplies",
      "category_confidence": "0.941",
      "category_source": "classifier",
      "remittance": {
        "unstructured": "INV-2026-0423 – Acme Office Supplies SAS",
        "structured_reference": "RF18539007547034",
        "reference_type": "SCOR"
      },
      "fees": [
        {
          "type": "Standard Transfer fee",
          "amount": 0.50,
          "currency": "EUR"
        }
      ],
      "scheme": "SEPA",
      "raw_data": {
        "merchant_name": "Acme Office Supplies",
        "merchant_entity_id": "plaid_entity_abc123",
        "counterparties": [
          {
            "name": "Acme Office Supplies SAS",
            "type": "merchant",
            "confidence_level": "VERY_HIGH"
          }
        ],
        "personal_finance_category": {
          "primary": "GENERAL_MERCHANDISE",
          "detailed": "GENERAL_MERCHANDISE_OFFICE_SUPPLIES"
        }
      },
      "created_at": "2026-05-14T09:32:05.123Z",
      "updated_at": "2026-05-14T09:35:11.456Z",
      "deleted_at": null
    },
    "relationships": {
      "workspace": {
        "data": { "type": "workspace", "id": "a1b2c3d4-0000-0000-0000-ffffffffffff" }
      },
      "debtor_payment_means": {
        "data": { "type": "payment_means", "id": "pm-0001-0000-0000-000000000001" }
      },
      "creditor_payment_means": {
        "data": { "type": "payment_means", "id": "pm-0002-0000-0000-000000000002" }
      },
      "account_balance": {
        "data": { "type": "account_balance", "id": "ab-0001-0000-0000-000000000001" }
      },
      "source_workspace_connector": {
        "data": { "type": "workspace_connector", "id": "wc-0001-0000-0000-000000000001" }
      },
      "ledger_account": {
        "data": null
      },
      "transaction_documents": {
        "data": []
      },
      "transaction_workspace_connectors": {
        "data": []
      }
    }
  }
}
Source: apps/api/src/database/entities/Transaction.ts · domain: financial-graph · tier: Main