> ## 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.

# Transaction

> 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

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.

| Naming                          | Value              |
| ------------------------------- | ------------------ |
| Object                          | Transaction        |
| Resource type (JSON:API `type`) | `transaction`      |
| Collection / records root       | `transactions`     |
| REST base                       | `/v1/transactions` |
| Entity class                    | `Transaction`      |

## API operations

| Operation | Method & path                  | Status        |
| --------- | ------------------------------ | ------------- |
| List      | `GET /v1/transactions`         | ✅ Implemented |
| Retrieve  | `GET /v1/transactions/{id}`    | ✅ Implemented |
| Create    | `POST /v1/transactions`        | 🟡 Planned    |
| Update    | `PATCH /v1/transactions/{id}`  | 🟡 Planned    |
| Delete    | `DELETE /v1/transactions/{id}` | 🟡 Planned    |

## Data model

### Attributes

| Field                      | Type                                                                                                                       | Required | Constraints                                                                                                                                                                                                                                                                                                                                     | Allowed values                                                                                                                                                                                                                                                                                                                                                            | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| transaction\_id            | string, UUID, 🔒 system                                                                                                    | ✅ Yes    | unique; generated via gen\_random\_uuid()                                                                                                                                                                                                                                                                                                       | —                                                                                                                                                                                                                                                                                                                                                                         | Public immutable identifier for the transaction. Exposed on all API responses; used for deduplication and external references.                                                                                                                                                                                                                                                                                                                                                                                                             |
| type                       | string (text, CHECK constraint)                                                                                            | ⚪ No     | nullable; 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 transaction                                      | High-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.                                                                                                                                                                                                                                                                                       |
| status                     | string (text, CHECK constraint)                                                                                            | ⚪ No     | nullable; 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 completion                                                               | Lifecycle 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\_id  | string                                                                                                                     | ⚪ No     | nullable; length ≤ 255; indexed (idx\_transactions\_external\_id) for connector sync batch dedup                                                                                                                                                                                                                                                | —                                                                                                                                                                                                                                                                                                                                                                         | Provider-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\_date | date                                                                                                                       | ⚪ No     | nullable; 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\_at               | timestamp                                                                                                                  | ✅ Yes    | NOT 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\_date              | date                                                                                                                       | ⚪ No     | nullable; stored as PostgreSQL DATE                                                                                                                                                                                                                                                                                                             | —                                                                                                                                                                                                                                                                                                                                                                         | Date 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\_date                | date                                                                                                                       | ⚪ No     | nullable; stored as PostgreSQL DATE                                                                                                                                                                                                                                                                                                             | —                                                                                                                                                                                                                                                                                                                                                                         | Date 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\_amount         | object (JSONB) — \{ amount: number; currency: CurrencyCodeEnum }                                                           | ✅ Yes    | NOT NULL; JSONB                                                                                                                                                                                                                                                                                                                                 | —                                                                                                                                                                                                                                                                                                                                                                         | The 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\_amount         | object (JSONB) — \{ amount: number; currency: CurrencyCodeEnum }                                                           | ⚪ No     | nullable; JSONB                                                                                                                                                                                                                                                                                                                                 | —                                                                                                                                                                                                                                                                                                                                                                         | The 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\_exchange          | object (JSONB) — \{ rate: number; pair: string; source: CurrencyRateSourceEnum; at: Date }                                 | ⚪ No     | nullable; JSONB                                                                                                                                                                                                                                                                                                                                 | source: ECB, FED, IMF, XE, OANDA, BANK, EXCHANGE\_RATE\_API, MANUAL, OTHER                                                                                                                                                                                                                                                                                                | FX 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\_purpose          | string                                                                                                                     | ⚪ No     | nullable; length ≤ 10                                                                                                                                                                                                                                                                                                                           | —                                                                                                                                                                                                                                                                                                                                                                         | ISO 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\_code              | string                                                                                                                     | ⚪ No     | nullable; length ≤ 10                                                                                                                                                                                                                                                                                                                           | —                                                                                                                                                                                                                                                                                                                                                                         | ISO 20022 purpose code providing additional detail beyond category\_purpose (e.g. SUPP = supplier payment, SALA = salary, RENT = rental payment). At most 10 characters.                                                                                                                                                                                                                                                                                                                                                                   |
| category\_normalized       | string (text)                                                                                                              | ⚪ No     | nullable; 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\_confidence       | string (decimal 4,3)                                                                                                       | ⚪ No     | nullable; DB CHECK ∈ \[0, 1] (transactions\_category\_confidence\_range); must be non-null iff category\_source = 'classifier'; forced to NULL on user override writes                                                                                                                                                                          | —                                                                                                                                                                                                                                                                                                                                                                         | Classifier 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\_source           | string (enum CategorySourceEnum)                                                                                           | ⚪ No     | nullable; native PG enum category\_source\_enum; invariant: classifier ⟺ category\_confidence IS NOT NULL                                                                                                                                                                                                                                       | classifier, user, connector, rule                                                                                                                                                                                                                                                                                                                                         | Provenance 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).                                                                                                                                                                                                                                                 |
| remittance                 | object (JSONB) — \{ unstructured?: string; structured\_reference?: string; reference\_type?: RemittanceReferenceTypeEnum } | ⚪ No     | nullable; JSONB; the unstructured field is the primary bank-discovery surface for PSD2/Qonto flows                                                                                                                                                                                                                                              | reference\_type: SCOR, QRR, ISR, IREF, EREF, PREF, MREF, CRED, USTD, NON                                                                                                                                                                                                                                                                                                  | Payment 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.                                                                                                                                                                                                    |
| fees                       | array (JSONB) — Array\<\{ type: TransactionFeeTypeEnum; amount: number; currency: CurrencyCodeEnum }>                      | ⚪ No     | nullable; JSONB array                                                                                                                                                                                                                                                                                                                           | type 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 fee | Breakdown 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.                                                                                                                                                                                                 |
| scheme                     | string (enum TransactionSchemeEnum)                                                                                        | ⚪ No     | nullable; native PG enum transaction\_scheme\_enum                                                                                                                                                                                                                                                                                              | SEPA, SWIFT, ACH, FASTER\_PAYMENTS, BACS, WIRE, OTHER                                                                                                                                                                                                                                                                                                                     | Payment 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\_data                  | object (JSONB) — Record\<string, unknown>                                                                                  | ⚪ No     | nullable; JSONB; no GIN index — queried via full-document ILIKE/::text cast in counterparty-bank discovery queries Q7/Q10/Q11                                                                                                                                                                                                                   | —                                                                                                                                                                                                                                                                                                                                                                         | Connector-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\_at                | timestamp, 🔒 system                                                                                                       | ✅ Yes    | NOT NULL; set by onCreate lifecycle hook                                                                                                                                                                                                                                                                                                        | —                                                                                                                                                                                                                                                                                                                                                                         | Timestamp 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\_at                | timestamp, 🔒 system                                                                                                       | ⚪ No     | nullable; set by onCreate and onUpdate lifecycle hooks                                                                                                                                                                                                                                                                                          | —                                                                                                                                                                                                                                                                                                                                                                         | Timestamp of the most recent update to this row (category assignment, enrichment, soft-delete). Set automatically by MikroORM on every flush.                                                                                                                                                                                                                                                                                                                                                                                              |
| deleted\_at                | timestamp                                                                                                                  | ⚪ No     | nullable; soft-delete sentinel; all active queries must filter deleted\_at IS NULL; partial indexes use WHERE deleted\_at IS NULL                                                                                                                                                                                                               | —                                                                                                                                                                                                                                                                                                                                                                         | Soft-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

| Name                           | Type                                    | Required                                                                       | Description                                                                                                                                                                                                                                                                                               |
| ------------------------------ | --------------------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| workspace                      | to-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\_means         | to-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\_means       | to-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\_balance               | to-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.                      |
| sourceWorkspaceConnector       | to-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\_account                | to-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\_documents         | to-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.                                                                                                                 |
| transactionWorkspaceConnectors | to-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

```json theme={null}
{
  "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": []
      }
    }
  }
}
```

<sub>Source: `apps/api/src/database/entities/Transaction.ts` · domain: financial-graph · tier: Main</sub>
