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

ModeDetect ingressDetection runResult egress
Per account (false, default)One message per account, with a single-element accounts[]One account per detect messageOne 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 messageOne 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_IDDetect messages publishedTypical use
false (default)One message per account, with a single-element accounts arraySpreads detection work across more messages; each message targets one account.
trueOne message per user, with every account for that user in the current batch in accountsAligns 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_COMPLETIONBehaviour
false (default)Only regular-transactions result messages are published. Affordability must be triggered separately by publishing to affordability-calculate.
trueAfter 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)

Deployment topologyTopic nameDefault consumer group ID
Internal brokerregular-transactions-detectregular-transactions-detect
External brokerexternal-regular-transactions-detectexternal-regular-transactions-detect

The Kafka message value must be a JSON object:

FieldTypeOptionalDescription
userIdString (max 36)NoUser identifier whose accounts are being processed.
accountsArray<Object>NoAccounts to process. At least one entry is required.

Each element of accounts[]:

FieldTypeOptionalDescription
accountIdString (max 36)NoAccount identifier to detect series for.
modeString (max 11)YesDetection mode: full (default) or incremental.
updatedAtStringYesISO 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)

Deployment topologyTopic nameDefault consumer group ID
Internal brokerregular-transactions-resultregular-transactions-result
External brokerexternal-regular-transactions-resultexternal-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:

FieldTypeOptionalDescription
userIdString (max 36)NoUser identifier that was requested for the detection run.
accountsArray<Object>NoDetection 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[])

FieldTypeOptionalDescription
accountIdString (max 36)NoAccount identifier for which series were computed.
predictionsArray<Object>NoArray of regular transaction series objects (schema below). May be empty.

Series object (predictions[])

Each element matches the object built in the worker's result-formatting stage.

FieldTypeOptionalDescription
seriesIdString (max 36)NoThe ID of the regular transaction series. UUID format.
accountIdString (max 36)NoThe ID of the account the transactions belong to. Matches the parent accounts[].accountId.
userIdString (max 36)NoThe ID of the user the transactions belong to. Matches the envelope userId.
typeString (max 7)NoWhether the series contains income or payment transactions (income or payment).
subtypeString (max 21)YesSubtype: directDebit, standingOrder, frequentVisit, committedCardPayment, or other.
frequencyString (max 11)NoFrequency: yearly, quarterly, monthly, fortnightly, or weekly.
descriptionString (max 256)NoThe full description of the latest transaction in the series. Same limit as enriched transaction description.
cleanedDescriptionString (max 256)NoThe cleaned description of the latest transaction in the series.
numTxMatchedInSeriesIntegerNoThe number of transactions contained in the series.
gapLengthInFreqUnitsIntegerNoIf the series contains a gap, the number of whole frequency units that are missing.
dateAnomaliesCountIntegerNoThe number of date anomalies detected in the series.
numReturnedTxsIntegerNoThe number of returned transactions (reversed or declined) contained in the series.
lastDateString (max 10)NoThe date of the latest transaction in the series (yyyy-MM-dd).
predictedDateString (max 10)NoThe predicted date of the next transaction in the series (yyyy-MM-dd).
predictedDateEarliestString (max 10)YesThe earliest predicted date at which the next transaction could occur (null if not set).
predictedDateLatestString (max 10)YesThe latest predicted date at which the next transaction could occur (null if not set).
predictedTxLateOrNotDetectedBooleanYesWhether the next predicted transaction is overdue or not detected. May be omitted if not set.
predictedAmountNumberNoThe predicted amount of the next transaction (as emitted by detection; currency base units where applicable).
predictedAmountLowerNumberYesLower bound of the predicted amount.
predictedAmountUpperNumberYesUpper bound of the predicted amount.
currencyString (max 3)YesISO 4217 currency code. Same limit as enriched transaction currency.
counterpartyIdString (max 36)YesL3 counterparty identifier from the first matched transaction that has counterparty enrichment. UUID format.
predictedCategoryIdString (max 3)NoThe ID of the predicted category. Same limit as enriched transaction l2CategoryId.
l3CounterpartyCategoryString (max 32)YesThe counterparty analysis category identifier. Same limit as counterparty l3CounterpartyCategory. May be null.
transactionsArray<Object>NoMatched historical transactions in the series (schema below).

Transaction object (transactions[])

FieldTypeOptionalDescription
uidString (max 36)NoTransaction identifier.
dateString (max 10)NoTransaction date (yyyy-MM-dd).
descriptionString (max 256)NoTransaction description.
amountNumberNoTransaction amount.
categoryIdString (max 3)NoL2 category identifier.
cleanedDescriptionString (max 256)NoCleaned description.
txCodeString (max 64)YesProvider transaction type code.
cardPresentBooleanYesWhether the transaction was card-present.
l3CounterpartyCategoryString (max 32)YesCounterparty 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": []
    }
  ]
}

Did this page help you?