Regular Transactions Schema for Egress
Field reference for user-scoped regular transaction series detect and result messages on Kafka.
Overview
Regular transaction series detection is user-scoped: every detect and result message includes a userId, and result egress is always shaped as { userId, accounts[] }. A user may have multiple accounts (for example, a current account and a credit card).
How many accounts are processed in each detect message — and therefore how detection is batched — is configurable. The daily regular-transactions orchestrator controls this via REGULAR_TRANSACTIONS_GROUP_BY_USER_ID (see Orchestrator detect batching):
| Mode | Detect ingress | Detection run | Result egress |
|---|---|---|---|
Per account (false, default) | One message per account, with a single-element accounts[] | One account per detect message | One result message per detect message, with a single-element accounts[] |
Per user (true) | One message per user, with every account for that user in accounts[] | All listed accounts in one detect message | One result message per detect message, with every processed account in accounts[] |
Manual publishes to the detect topic may use either shape, provided accounts contains at least one entry.
To request detection, publish a message to the regular-transactions detect topic. The worker reads transactions for each listed account, detects series, persists predictions, and publishes a user-scoped result to the regular-transactions result topic.
This mirrors the affordability interface: one userId in, one user-scoped message out per detect message processed.
Configuration
Several deployment options affect how detect messages are produced and whether affordability is triggered automatically. These are configured by Moneyhub for your environment; see List of configurable features.
Orchestrator detect batching
Controlled by REGULAR_TRANSACTIONS_GROUP_BY_USER_ID on the orchestrator (default: false).
The daily regular-transactions orchestrator publishes detect messages to the regular-transactions-detect topic. The message schema is always { userId, accounts[] }, but the number of accounts in each message is configurable:
REGULAR_TRANSACTIONS_GROUP_BY_USER_ID | Detect messages published | Typical use |
|---|---|---|
false (default) | One message per account, with a single-element accounts array | Spreads detection work across more messages; each message targets one account. |
true | One message per user, with every account for that user in the current batch in accounts | Aligns detect ingress with user-scoped result egress; fewer messages per user. |
In per user mode, the orchestrator also pages and buckets by userId rather than accountId.
Example (detect message — per account mode)
When batching is set to per account, each detect message contains one account:
{
"userId": "550e8400-e29b-41d4-a716-446655440000",
"accounts": [
{
"accountId": "090ea264-eb48-494c-9d59-1b10b322c3e1",
"mode": "full"
}
]
}Example (detect message — per user mode)
When batching is set to per user, one message lists every account for that user in the batch:
{
"userId": "550e8400-e29b-41d4-a716-446655440000",
"accounts": [
{
"accountId": "090ea264-eb48-494c-9d59-1b10b322c3e1",
"mode": "full"
},
{
"accountId": "de4be182-efb4-4aad-b221-fa29631e6653",
"mode": "incremental",
"updatedAt": "2026-06-01T12:00:00.000Z"
}
]
}Manual publishes to the detect topic may use either shape, provided accounts contains at least one entry.
Affordability auto-trigger
Controlled by ENABLE_REGULAR_TRANSACTIONS_REQUEST_AFFORDABILITY_REPORT_ON_COMPLETION on the regular-transactions worker (default: false).
The regular-transactions worker can be configured to automatically request an affordability report after it successfully processes a detect message for a user.
ENABLE_REGULAR_TRANSACTIONS_REQUEST_AFFORDABILITY_REPORT_ON_COMPLETION | Behaviour |
|---|---|
false (default) | Only regular-transactions result messages are published. Affordability must be triggered separately by publishing to affordability-calculate. |
true | After a successful detect run, the worker also publishes {"userId": "<uuid>"} to the affordability-calculate topic for the same user. |
When enabled, one affordability calculate message is sent per user detect message processed, regardless of how many accounts were in accounts[]. See Affordability result schema for the calculate and result message formats.
Request message (regular-transactions-detect)
regular-transactions-detect)| Deployment topology | Topic name | Default consumer group ID |
|---|---|---|
| Internal broker | regular-transactions-detect | regular-transactions-detect |
| External broker | external-regular-transactions-detect | external-regular-transactions-detect |
The Kafka message value must be a JSON object:
| Field | Type | Optional | Description |
|---|---|---|---|
| userId | String (max 36) | No | User identifier whose accounts are being processed. |
| accounts | Array<Object> | No | Accounts to process. At least one entry is required. |
Each element of accounts[]:
| Field | Type | Optional | Description |
|---|---|---|---|
| accountId | String (max 36) | No | Account identifier to detect series for. |
| mode | String (max 11) | Yes | Detection mode: full (default) or incremental. |
| updatedAt | String | Yes | ISO 8601 timestamp. Required when mode is incremental; ignored for full. |
In full mode the worker loads the configured retention window of transaction history. In incremental mode it loads transactions from updatedAt onwards and merges with existing predictions.
The message key should be a unique identifier (UUID recommended). It is copied to the corresponding result message.
See Configuration for how the orchestrator groups accounts when publishing detect messages.
Result topic (regular-transactions-result)
regular-transactions-result)| Deployment topology | Topic name | Default consumer group ID |
|---|---|---|
| Internal broker | regular-transactions-result | regular-transactions-result |
| External broker | external-regular-transactions-result | external-regular-transactions-result |
Topic names are configurable per environment. Failed messages are sent to the regular-transactions dead-letter topic (regular-transactions-dead-letter / external-regular-transactions-dead-letter); see Errors for the general dead-letter envelope shape.
Result message envelope
The Kafka message value is a JSON object produced by the regular-transactions worker after processing a regular-transactions-detect message:
| Field | Type | Optional | Description |
|---|---|---|---|
| userId | String (max 36) | No | User identifier that was requested for the detection run. |
| accounts | Array<Object> | No | Detection results per account (schema below). One entry per account processed. |
The message key is copied from the triggering detect message when present; otherwise a new UUID is generated.
Account result (accounts[])
accounts[])| Field | Type | Optional | Description |
|---|---|---|---|
| accountId | String (max 36) | No | Account identifier for which series were computed. |
| predictions | Array<Object> | No | Array of regular transaction series objects (schema below). May be empty. |
Series object (predictions[])
predictions[])Each element matches the object built in the worker's result-formatting stage.
| Field | Type | Optional | Description |
|---|---|---|---|
| seriesId | String (max 36) | No | The ID of the regular transaction series. UUID format. |
| accountId | String (max 36) | No | The ID of the account the transactions belong to. Matches the parent accounts[].accountId. |
| userId | String (max 36) | No | The ID of the user the transactions belong to. Matches the envelope userId. |
| type | String (max 7) | No | Whether the series contains income or payment transactions (income or payment). |
| subtype | String (max 21) | Yes | Subtype: directDebit, standingOrder, frequentVisit, committedCardPayment, or other. |
| frequency | String (max 11) | No | Frequency: yearly, quarterly, monthly, fortnightly, or weekly. |
| description | String (max 256) | No | The full description of the latest transaction in the series. Same limit as enriched transaction description. |
| cleanedDescription | String (max 256) | No | The cleaned description of the latest transaction in the series. |
| numTxMatchedInSeries | Integer | No | The number of transactions contained in the series. |
| gapLengthInFreqUnits | Integer | No | If the series contains a gap, the number of whole frequency units that are missing. |
| dateAnomaliesCount | Integer | No | The number of date anomalies detected in the series. |
| numReturnedTxs | Integer | No | The number of returned transactions (reversed or declined) contained in the series. |
| lastDate | String (max 10) | No | The date of the latest transaction in the series (yyyy-MM-dd). |
| predictedDate | String (max 10) | No | The predicted date of the next transaction in the series (yyyy-MM-dd). |
| predictedDateEarliest | String (max 10) | Yes | The earliest predicted date at which the next transaction could occur (null if not set). |
| predictedDateLatest | String (max 10) | Yes | The latest predicted date at which the next transaction could occur (null if not set). |
| predictedTxLateOrNotDetected | Boolean | Yes | Whether the next predicted transaction is overdue or not detected. May be omitted if not set. |
| predictedAmount | Number | No | The predicted amount of the next transaction (as emitted by detection; currency base units where applicable). |
| predictedAmountLower | Number | Yes | Lower bound of the predicted amount. |
| predictedAmountUpper | Number | Yes | Upper bound of the predicted amount. |
| currency | String (max 3) | Yes | ISO 4217 currency code. Same limit as enriched transaction currency. |
| counterpartyId | String (max 36) | Yes | L3 counterparty identifier from the first matched transaction that has counterparty enrichment. UUID format. |
| predictedCategoryId | String (max 3) | No | The ID of the predicted category. Same limit as enriched transaction l2CategoryId. |
| l3CounterpartyCategory | String (max 32) | Yes | The counterparty analysis category identifier. Same limit as counterparty l3CounterpartyCategory. May be null. |
| transactions | Array<Object> | No | Matched historical transactions in the series (schema below). |
Transaction object (transactions[])
transactions[])| Field | Type | Optional | Description |
|---|---|---|---|
| uid | String (max 36) | No | Transaction identifier. |
| date | String (max 10) | No | Transaction date (yyyy-MM-dd). |
| description | String (max 256) | No | Transaction description. |
| amount | Number | No | Transaction amount. |
| categoryId | String (max 3) | No | L2 category identifier. |
| cleanedDescription | String (max 256) | No | Cleaned description. |
| txCode | String (max 64) | Yes | Provider transaction type code. |
| cardPresent | Boolean | Yes | Whether the transaction was card-present. |
| l3CounterpartyCategory | String (max 32) | Yes | Counterparty analysis category identifier. May be null when not available. |
Example (result message body)
{
"userId": "550e8400-e29b-41d4-a716-446655440000",
"accounts": [
{
"accountId": "090ea264-eb48-494c-9d59-1b10b322c3e1",
"predictions": [
{
"seriesId": "45f004dc-254f-5d09-b07f-8d4de4487781",
"accountId": "090ea264-eb48-494c-9d59-1b10b322c3e1",
"userId": "550e8400-e29b-41d4-a716-446655440000",
"type": "payment",
"subtype": "frequentVisit",
"frequency": "quarterly",
"description": "Dentist Checkup",
"cleanedDescription": "Dentist Checkup",
"numTxMatchedInSeries": 4,
"gapLengthInFreqUnits": 0,
"dateAnomaliesCount": 0,
"numReturnedTxs": 0,
"lastDate": "2026-01-20",
"predictedDate": "2026-04-20",
"predictedDateEarliest": "2026-04-20",
"predictedDateLatest": "2026-04-21",
"predictedTxLateOrNotDetected": false,
"predictedAmount": -65,
"predictedAmountLower": -65,
"predictedAmountUpper": -65,
"currency": "GBP",
"counterpartyId": "203d5a9a-d4dd-5ce5-ab60-60f740336dea",
"predictedCategoryId": "11",
"l3CounterpartyCategory": null,
"transactions": [
{
"uid": "TXN-090ea264-775",
"date": "2026-01-20",
"description": "Dentist Checkup",
"amount": -65,
"categoryId": "11",
"cleanedDescription": "Dentist Checkup",
"txCode": null,
"cardPresent": false,
"l3CounterpartyCategory": null
},
{
"uid": "TXN-090ea264-774",
"date": "2025-10-20",
"description": "Dentist Checkup",
"amount": -65,
"categoryId": "11",
"cleanedDescription": "Dentist Checkup",
"txCode": null,
"cardPresent": false,
"l3CounterpartyCategory": null
}
]
}
]
},
{
"accountId": "de4be182-efb4-4aad-b221-fa29631e6653",
"predictions": []
}
]
}Updated 10 days ago
