Appearance
Accounts CRUD
Financial account management: creation, querying, updates, and atomic balance tracking.
Overview
| Attribute | Value |
|---|---|
| Service | src/service/AccountService.ts |
| Interface | src/repository/IAccountService.ts |
| Storage | DynamoDB AccountsTable |
| Schemas | src/schema/account.json, create_account_request.json, update_account_request.json |
Account Model
| Field | Type | Description |
|---|---|---|
PK | string | Partition key: ENT#{entity_id}#CUR#{currency} (BUSINESS/PLATFORM) or ACQ#{acquirer}#CUR#{currency} (ACQUIRER) |
SK | string | Sort key: ACCT#{account_code} |
id | UUID | Unique account identifier |
account_number | string | 10-digit human-readable number with Luhn check digit |
account_code | AccountCodeEnum | Standardized code (e.g., BUSINESS_PAYABLE, ACQUIRER_RECEIVABLE) |
name | string | Display name |
type | AccountTypeEnum | ASSET, LIABILITY, REVENUE, EXPENSE, or EQUITY |
entity_type | EntityTypeEnum | BUSINESS, ACQUIRER, or PLATFORM |
entity_id | string | Business ID, acquirer name, or T1 for platform |
acquirer | string? | Acquirer name (only for ACQUIRER entity type) |
currency_code | string | ISO currency code (e.g., MXN, USD) |
status | string | Account status (e.g., active, inactive) |
balance | number | Current balance (updated atomically via ledger entries only) |
version | number | Optimistic concurrency version counter |
metadata | object? | Additional key-value data |
created_at | number | Creation timestamp (epoch ms) |
modified_at | number | Last modification timestamp (epoch ms) |
last_transaction_at | number? | Last activity timestamp |
deleted_at | number? | Soft-delete timestamp |
PK/SK Pattern
| Entity Type | PK Format | Example |
|---|---|---|
| BUSINESS | ENT#{entity_id}#CUR#{currency} | ENT#abc123#CUR#MXN |
| ACQUIRER | ACQ#{acquirer}#CUR#{currency} | ACQ#kushki#CUR#MXN |
| PLATFORM | ENT#T1#CUR#{currency} | ENT#T1#CUR#MXN |
An alias item is also created: PK = ACCOUNT_NUMBER#{account_number}, SK = ALIAS. This enables lookups by account_number via the account_number-index GSI.
Account Number Generation
Format: <prefix><4-digit issuer hash><4-random digits><Luhn check digit>
| Entity Type | Prefix | Example |
|---|---|---|
| BUSINESS | 1 | 1-4382-7291-3 |
| ACQUIRER | 2 | 2-8174-0562-8 |
| PLATFORM | 9 | 9-0001-3847-5 |
The Luhn check digit (mod 10 algorithm) is appended as the 10th digit for validation.
Endpoints
Create Account
POST /v1/accounts
| Field | Required | Description |
|---|---|---|
account_code | Yes | One of AccountCodeEnum values |
name | Yes | Display name |
currency_code | Yes | ISO currency code |
entity_type | Yes | BUSINESS, ACQUIRER, or PLATFORM |
entity_id | Conditional | Required when entity_type = BUSINESS |
acquirer | Conditional | Required when entity_type = ACQUIRER |
status | No | Defaults to active |
balance | No | Initial balance (default 0) |
metadata | No | Additional data |
additional_accounts | No | Array of account objects to create in batch |
Batch creation: The additional_accounts array creates multiple accounts in a single request. Each entry follows the same schema. All accounts are created in a DynamoDB transactWrite operation.
type and entity_type per account code: The API schema requires type (ASSET / LIABILITY / REVENUE / EXPENSE / EQUITY) and entity_type (BUSINESS / ACQUIRER / PLATFORM) to be passed explicitly in the create request. Each account_code has a canonical expected type and entity — the backoffice form auto-fills these based on the code, but direct API callers must supply them correctly. See the authoritative mapping in the Domain Glossary — Account Codes section.
List Accounts
GET /v1/accounts
Query parameters:
| Parameter | Required | Description |
|---|---|---|
from_date | Yes | Start date filter (ISO string) |
to_date | Yes | End date filter (ISO string) |
entity_type | No | Filter by BUSINESS, ACQUIRER, PLATFORM |
entity_id | No | Filter by entity |
acquirer | No | Filter by acquirer |
account_code | No | Filter by account code |
currency_code | No | Filter by currency |
status | No | Filter by status |
sort | No | Sort field: created_at, modified_at, account_number, name, type, status |
sort_order | No | asc or desc |
limit | No | Page size |
next_token | No | Pagination token |
Get Account by Number
GET /v1/accounts/{account_number}
Looks up the account via the account_number-index GSI, then fetches the full item.
Get Account by Code
GET /v1/accounts/by-code
| Parameter | Required | Description |
|---|---|---|
account_code | Yes | AccountCodeEnum value |
currency_code | Yes | ISO currency code |
entity_id | No | Entity ID (for BUSINESS accounts) |
acquirer | No | Acquirer name (for ACQUIRER accounts) |
Constructs the PK/SK directly from the parameters and fetches with getItem.
Update Account
PATCH /v1/accounts/{account_number}
Updatable fields only:
| Field | Description |
|---|---|
name | Display name |
entity_type | Entity classification |
status | Account status |
metadata | Additional data |
Important:
balanceis NOT updatable via this endpoint. Balance changes happen exclusively through ledger entries processed byaccountUpdater.
Delete Account
DELETE /v1/accounts/{account_number}
Soft-deletes by setting deleted_at timestamp.
Balance Update Mechanism
Account balances are updated atomically through the AccountUpdateQueue (FIFO SQS):
AccountingServicegenerates ledger entries and sends them toAccountUpdateQueue- Entries are grouped by
account_id, batched in groups of 10, withMessageGroupId = account.id(FIFO ordering per account) accountUpdaterprocesses entries sequentially per account:- Uses DynamoDB
ADDoperator (notSET) forbalanceandversion— atomic, race-condition safe - Calculates
balance_account_beforeandbalance_account_afterper entry - Assigns sequential
seqnumber per account - Processes in batches of 99 entries per
transactWrite(limit is 100; 1 slot reserved for the account update)
- Uses DynamoDB
Balance Delta Rules
| Account Type | DEBIT | CREDIT |
|---|---|---|
| ASSET | +amount | −amount |
| EXPENSE | +amount | −amount |
| LIABILITY | −amount | +amount |
| REVENUE | −amount | +amount |
| EQUITY | −amount | +amount |
REVERSAL entries invert the sign.
Ledger Entry TTL
Ledger entries in DynamoDB have a TTL of 15 days (LEDGER_ENTRY_TTL_DAYS). After expiration, entries are archived to Firehose and deleted from the table. Historical entries persist in MongoDB.
Non-Obvious Behaviors
- 160 retries on AccountUpdateQueue:
maxReceiveCount = 160is intentional. Losing a balance update causes permanent inconsistency. Never lower this value. - accountUpdater concurrency: 10:
maximumConcurrency: 10on the SQS event source limits concurrent DynamoDB writes during bulk operations. - Version counter: Incremented atomically with
ADD. Used for optimistic concurrency on the account item. - Alias item: Every account has a companion item (
ACCOUNT_NUMBER#X / ALIAS) for O(1) lookup by account number.