Skip to content

Accounts CRUD

Financial account management: creation, querying, updates, and atomic balance tracking.


Overview

AttributeValue
Servicesrc/service/AccountService.ts
Interfacesrc/repository/IAccountService.ts
StorageDynamoDB AccountsTable
Schemassrc/schema/account.json, create_account_request.json, update_account_request.json

Account Model

FieldTypeDescription
PKstringPartition key: ENT#{entity_id}#CUR#{currency} (BUSINESS/PLATFORM) or ACQ#{acquirer}#CUR#{currency} (ACQUIRER)
SKstringSort key: ACCT#{account_code}
idUUIDUnique account identifier
account_numberstring10-digit human-readable number with Luhn check digit
account_codeAccountCodeEnumStandardized code (e.g., BUSINESS_PAYABLE, ACQUIRER_RECEIVABLE)
namestringDisplay name
typeAccountTypeEnumASSET, LIABILITY, REVENUE, EXPENSE, or EQUITY
entity_typeEntityTypeEnumBUSINESS, ACQUIRER, or PLATFORM
entity_idstringBusiness ID, acquirer name, or T1 for platform
acquirerstring?Acquirer name (only for ACQUIRER entity type)
currency_codestringISO currency code (e.g., MXN, USD)
statusstringAccount status (e.g., active, inactive)
balancenumberCurrent balance (updated atomically via ledger entries only)
versionnumberOptimistic concurrency version counter
metadataobject?Additional key-value data
created_atnumberCreation timestamp (epoch ms)
modified_atnumberLast modification timestamp (epoch ms)
last_transaction_atnumber?Last activity timestamp
deleted_atnumber?Soft-delete timestamp

PK/SK Pattern

Entity TypePK FormatExample
BUSINESSENT#{entity_id}#CUR#{currency}ENT#abc123#CUR#MXN
ACQUIRERACQ#{acquirer}#CUR#{currency}ACQ#kushki#CUR#MXN
PLATFORMENT#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 TypePrefixExample
BUSINESS11-4382-7291-3
ACQUIRER22-8174-0562-8
PLATFORM99-0001-3847-5

The Luhn check digit (mod 10 algorithm) is appended as the 10th digit for validation.


Endpoints

Create Account

POST /v1/accounts

FieldRequiredDescription
account_codeYesOne of AccountCodeEnum values
nameYesDisplay name
currency_codeYesISO currency code
entity_typeYesBUSINESS, ACQUIRER, or PLATFORM
entity_idConditionalRequired when entity_type = BUSINESS
acquirerConditionalRequired when entity_type = ACQUIRER
statusNoDefaults to active
balanceNoInitial balance (default 0)
metadataNoAdditional data
additional_accountsNoArray 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:

ParameterRequiredDescription
from_dateYesStart date filter (ISO string)
to_dateYesEnd date filter (ISO string)
entity_typeNoFilter by BUSINESS, ACQUIRER, PLATFORM
entity_idNoFilter by entity
acquirerNoFilter by acquirer
account_codeNoFilter by account code
currency_codeNoFilter by currency
statusNoFilter by status
sortNoSort field: created_at, modified_at, account_number, name, type, status
sort_orderNoasc or desc
limitNoPage size
next_tokenNoPagination 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

ParameterRequiredDescription
account_codeYesAccountCodeEnum value
currency_codeYesISO currency code
entity_idNoEntity ID (for BUSINESS accounts)
acquirerNoAcquirer 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:

FieldDescription
nameDisplay name
entity_typeEntity classification
statusAccount status
metadataAdditional data

Important: balance is NOT updatable via this endpoint. Balance changes happen exclusively through ledger entries processed by accountUpdater.

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):

  1. AccountingService generates ledger entries and sends them to AccountUpdateQueue
  2. Entries are grouped by account_id, batched in groups of 10, with MessageGroupId = account.id (FIFO ordering per account)
  3. accountUpdater processes entries sequentially per account:
    • Uses DynamoDB ADD operator (not SET) for balance and version — atomic, race-condition safe
    • Calculates balance_account_before and balance_account_after per entry
    • Assigns sequential seq number per account
    • Processes in batches of 99 entries per transactWrite (limit is 100; 1 slot reserved for the account update)

Balance Delta Rules

Account TypeDEBITCREDIT
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 = 160 is intentional. Losing a balance update causes permanent inconsistency. Never lower this value.
  • accountUpdater concurrency: 10: maximumConcurrency: 10 on 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.

Vecnet — Build Spec v0.2 · Obsidian Terminal