Account
Account represents a bank or financial account (deposit, credit, loan, investment, payroll, or other) held by a workspace, company, or individual. It is the primary model the connector layer (Plaid, Qonto, etc.) writes synced bank accounts into, and it is also used to record counterparty accounts extracted from invoice payment means. Key associations are to Workspace (tenant scope), Company (account holder or the workspace's own company), People (individual holder), a bank Company via bank_company, a source WorkspaceConnector tracking provenance, and one-to-many AccountWorkspaceConnectors for multi-connector sync audit. The ownership field distinguishes workspace-held accounts from counterparty accounts from unclassified legacy rows.
| Naming | Value |
|---|---|
| Object | Account |
Resource type (JSON:API type) | account |
| Collection / records root | accounts |
| REST base | /v1/accounts |
| Entity class | Account |
API operations
| Operation | Method & path | Status |
|---|---|---|
| List | GET /v1/accounts | โ Implemented |
| Retrieve | GET /v1/accounts/{id} | โ Implemented |
| Create | POST /v1/accounts | ๐ก Planned |
| Update | PATCH /v1/accounts/{id} | ๐ก Planned |
| Delete | DELETE /v1/accounts/{id} | ๐ก Planned |
Data model
Attributes
| Field | Type | Required | Constraints | Allowed values | Description |
|---|---|---|---|---|---|
| account_id | string, UUID, ๐ system | โ Yes | unique; generated via gen_random_uuid() on INSERT | โ | Public immutable identifier for the account. Exposed in all API responses and external references. |
| account_external_id | string | โช No | max length 255; partial unique index on (workspace_pk, account_external_id) WHERE deleted_at IS NULL AND account_external_id IS NOT NULL | โ | Provider-assigned identifier for the account (e.g. Plaid account_id, Qonto account external id). Used as the deduplication key during connector sync to prevent re-creating an already-ingested account. |
| type | string (enum) | โ Yes | non-null; maps to native pg enum account_type_enum | deposit, credit, loan, investment, payroll, other | Top-level account classification. deposit and credit are the most common for operational bank accounts; investment covers treasury / brokerage accounts (e.g. Arc Treasury via Pershing). |
| subtype | string (enum) | โช No | nullable; maps to native pg enum account_subtype_enum | checking account, savings account, money market account, cash management, certificate of deposit, electronic benefit transfer, health savings account, PayPal account โฆ +63 more (see account_subtype_enum enum) | Granular Plaid-compatible account subtype. Provides detailed classification within each account type. Not always populated for non-Plaid connectors. |
| account_name | string | โช No | max length 255; user-editable in some connectors โ must not be used as a unique key | โ | Human-readable display name for the account, typically provided by the bank or fintech (e.g. 'Qonto EUR โ Operating'). May contain the bank name as free text; use as display label only, not as an identity signal. |
| iban | string | โช No | max length 34; CHECK: iban IS NULL OR iban ~ '^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$' (ISO 13616) | โ | International Bank Account Number in ISO 13616 format. Fully populated by the Qonto connector; absent for US-centric connectors (Plaid/Mercury). The IBAN prefix encodes the country and CIB (FR IBANs: positions 5โ9 are the bank code). |
| account_number | string | โช No | max length 50 | โ | Domestic account number, used where IBAN is not applicable (e.g. US accounts). Stored as-is from the provider. |
| bic | string | โช No | max length 11; CHECK: bic IS NULL OR bic ~ '^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$' (ISO 9362, 8 or 11 chars) | โ | BIC/SWIFT code for the account's bank in ISO 9362 format. First 4 characters are the institution code. Used for bank identity resolution and cross-workspace deduplication. |
| routing_number | string | โช No | max length 9; CHECK: routing_number IS NULL OR routing_number ~ '^[0-9]{9}$' (US ABA 9-digit) | โ | US ABA routing number. Maps deterministically to a US bank via the FedACH directory. Populated for some US-side accounts; absent for EU/UK accounts. |
| sort_code | string | โช No | max length 6; CHECK: sort_code IS NULL OR sort_code ~ '^[0-9]{6}$' (UK 6-digit) | โ | UK bank sort code. Maps to a specific UK bank and branch. Absent for non-UK accounts. |
| currency | string | โช No | max length 3; CHECK: currency IS NULL OR currency ~ '^[A-Z]{3}$' (ISO 4217) | โ | ISO 4217 three-letter currency code for the primary currency of the account. Helps disambiguate multiple accounts at the same institution (e.g. Mercury USD vs Mercury IO). |
| digital_wallet_provider | string (enum) | โช No | nullable; maps to native pg enum digital_wallet_provider_enum | paypal, apple_pay, google_pay, samsung_pay, alipay, wechat_pay | Provider of the digital wallet when the account represents a digital wallet rather than a traditional bank account. |
| digital_wallet_id | string | โช No | max length 255 | โ | External identifier for the digital wallet account at the specified digital_wallet_provider. |
| digital_wallet_type | string (enum) | โช No | nullable; maps to native pg enum digital_wallet_type_enum | personal, business, merchant | Classification of the digital wallet account by usage context. |
| ownership | string (enum) | โ Yes | non-null; default 'unknown'; maps to native pg enum account_ownership_enum. Set at write time from structured signals only โ never from string/regex matching. | workspace, counterparty, unknown | Classifies whether the account belongs to the workspace itself (a connector-synced bank account) or to a counterparty (extracted from an invoice or document payment means). 'unknown' is the safe default for legacy rows and rows lacking sufficient provenance signal. Backfilled to 'workspace' where source_workspace_connector_pk is set or where a PaymentMeans links the account back to the workspace's own company. |
| raw_data | jsonb | โช No | nullable; shape varies by connector โ always branch on originating connector before parsing | โ | Complete connector-native payload as received from the provider. For Plaid: contains institution_id, institution_name, official_name, and counterparty metadata. Shape is connector-specific; do not assume a fixed path without checking the source connector. |
| created_at | Date, ๐ system | โ Yes | set once on INSERT via onCreate lifecycle hook | โ | Timestamp of record creation. Set automatically by MikroORM; never supplied by callers. |
| updated_at | Date, ๐ system | โช No | set on INSERT and refreshed on every UPDATE via onCreate/onUpdate lifecycle hooks | โ | Timestamp of last update. Refreshed automatically by MikroORM on every write. |
| deleted_at | Date | โช No | nullable; soft-delete sentinel. All queries must filter deleted_at IS NULL. The partial index idx_accounts_workspace_external_id_active uses WHERE deleted_at IS NULL. | โ | Soft-delete timestamp. When set, the account is logically deleted and excluded from all standard queries. Hard deletes are not performed on this entity. |
Relationships
| Name | Type | Required | Description |
|---|---|---|---|
| workspace | to-one (Workspace) | โช No (nullable FK) | Tenant scope. Points to the Workspace that owns this account. Every standard query must filter through this relation. Indexed via idx_accounts_workspace_deleted (workspace, deleted_at). |
| company | to-one (Company) | โช No (nullable FK, fieldName: company_pk) | The company that holds this account. For workspace-owned accounts, this is typically the workspace's own company. Mutually exclusive with the people relation in practice โ each account belongs to either a Company or a People. Indexed via idx_accounts_company. |
| people | to-one (People) | โช No (nullable FK, fieldName: people_pk) | The individual person who holds this account. Used when the account belongs to a natural person rather than a legal entity. Mutually exclusive with the company relation in practice. Indexed via idx_accounts_people. |
| bank_company | to-one (Company) | โช No (nullable FK, fieldName: bank_company_pk) | The bank institution (represented as a Company) that operates this account. Used for logo display and bank identity resolution. When NULL, the bank can sometimes be recovered from raw_data or inferred from IBAN/BIC. Indexed via idx_accounts_bank_company. |
| source_workspace_connector | to-one (WorkspaceConnector) | โช No (nullable FK) | The WorkspaceConnector instance that first created this account row. When non-NULL, indicates connector-confirmed provenance and is a primary signal for setting ownership = 'workspace'. When NULL, the account was created by a fallback path (manual, invoice parse, payment means extraction). |
| workspace_connector | to-one (WorkspaceConnector) | โช No (nullable FK, fieldName: workspace_connector_pk) | The WorkspaceConnector currently associated with this account for ongoing sync operations. Distinct from source_workspace_connector โ a different connector may currently manage sync even if it did not create the row. |
| account_workspace_connectors | to-many (AccountWorkspaceConnector) | โ | Collection of AccountWorkspaceConnector pivot rows recording per-connector sync provenance for this account, including the sync direction (inbound/outbound). Used for multi-connector audit trails. |
System-computed
- account_id: generated via gen_random_uuid() on INSERT; unique constraint enforced at the database level.
- created_at: set once on INSERT via MikroORM onCreate lifecycle hook; never supplied by callers.
- updated_at: set on INSERT and refreshed on every UPDATE via MikroORM onCreate/onUpdate lifecycle hooks.
- deleted_at: soft-delete sentinel. When set, the account is excluded from all standard queries. The partial index idx_accounts_workspace_external_id_active uses WHERE deleted_at IS NULL AND account_external_id IS NOT NULL for the Plaid-sync hot-path dedup lookup.
- ownership default: defaults to 'unknown' at the database level for all new rows. Backfilled to 'workspace' by Migration20260527120000_account_ownership where source_workspace_connector_pk IS NOT NULL, or where a PaymentMeans row links the account to the workspace's own company (payment_means.account_pk = accounts.pk AND payment_means.company_pk = workspace.own_company_pk).
- account_external_id dedup: the partial unique index idx_accounts_workspace_external_id_active on (workspace_pk, account_external_id) WHERE deleted_at IS NULL AND account_external_id IS NOT NULL is the connector-layer dedup key. Plaid sync uses this to find an existing account before creating a new one, preventing re-linking on reconnect.
- source_workspace_connector provenance: when source_workspace_connector_pk IS NOT NULL, the account was created by the connector pipeline and ownership should be 'workspace'. A NULL source_workspace_connector_pk means the account was created via a fallback path (manual entry, invoice payment means extraction) and ownership defaults to 'unknown' pending classification.
- bank_company recovery: when bank_company_pk IS NULL, the bank identity may be recoverable from raw_data (look for raw_data->>'institution_name' or raw_data->'institution'->>'name') or inferred from the IBAN bank code prefix (FR IBANs: positions 5โ9 = CIB) or BIC first 4 characters.
- Composite composites.yml definitions: two composites are defined for the accounts root โ composite_account_summary (source_fields: account_id, bank_company.primary_media.url, account_name, currency, iban, account_number; display_type: account_summary) and composite_latest_balance_currency (source_fields: account_id, account_balances.accounting_balance; display_type: currency_amount).
Example
{
"data": {
"type": "account",
"id": "a3f7c2e1-84b0-4d5e-9f12-6c8a1b0e3d7f",
"attributes": {
"account_external_id": "qonto_acc_EU84QNTO00001234567890",
"type": "deposit",
"subtype": "checking account",
"account_name": "Qonto EUR โ Operating",
"iban": "FR7616958000010000012345678",
"account_number": null,
"bic": "QNTOFRP1XXX",
"routing_number": null,
"sort_code": null,
"currency": "EUR",
"digital_wallet_provider": null,
"digital_wallet_id": null,
"digital_wallet_type": null,
"ownership": "workspace",
"raw_data": {
"institution_id": "ins_qonto",
"institution_name": "Qonto",
"official_name": "Qonto Business Current Account"
},
"created_at": "2025-03-14T09:22:11.000Z",
"updated_at": "2026-02-01T14:05:33.000Z",
"deleted_at": null
},
"relationships": {
"workspace": {
"data": { "type": "workspace", "id": "b1e9d4f2-3301-4a8c-b7e0-9f4c2d1a5e88" }
},
"company": {
"data": { "type": "company", "id": "c2a8f5e3-7712-4b9d-a1f2-8e3d0c6b4a21" }
},
"people": {
"data": null
},
"bank_company": {
"data": { "type": "company", "id": "d4b7e8f1-2290-4c3a-b5e7-1f9d0a2c8b64" }
},
"source_workspace_connector": {
"data": { "type": "workspace_connector", "id": "e5c9d1a2-4403-4e7b-c2f3-2a4b5d0e9c77" }
},
"workspace_connector": {
"data": { "type": "workspace_connector", "id": "e5c9d1a2-4403-4e7b-c2f3-2a4b5d0e9c77" }
},
"account_workspace_connectors": {
"data": [
{ "type": "account_workspace_connector", "id": "f6d0e2b3-5514-4f8c-d3g4-3b5c6e1f0d88" }
]
}
}
}
}apps/api/src/database/entities/Account.ts ยท domain: financial-graph ยท tier: Main