Affordability Schema

Field reference for user-scoped affordability calculate and result messages on Kafka.

Overview

Affordability reports are produced per user. A user may have multiple accounts (for example, a current account and a credit card); the report aggregates enriched transactions and regular-transaction predictions across all of that user's accounts.

To request a report, publish a message to the affordability calculate topic. A corresponding result is then published to the affordability result topic.

Moneyhub can also configure regular-transaction detection to request affordability automatically after a successful run, so manual calculate publishes are not always required. See List of configurable features.

Request message

The Kafka message value must be a JSON object:

FieldTypeOptionalDescription
userIdString (max 36)NoUser identifier for whom the report is requested.
asOfDateString (max 10)YesCalendar date (yyyy-MM-dd) the report should end on. See Dated report.

The message key should be a unique identifier (UUID recommended). It is copied to the corresponding result message.

Example (request message)

{
  "userId": "550e8400-e29b-41d4-a716-446655440000"
}

Example (report for a past date)

{
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "asOfDate": "2025-04-01"
}

Dated report

asOfDate calculates the same report for the 12 months ending on that calendar day, including the whole of that day. It uses the regular series stored for that date, from a dated detection with the same asOfDate. It does not use the current series.

The published report includes transactionsPeriod. On this dated request, transactionsPeriod.endDate is the asOfDate. A calculate request that omits asOfDate still includes transactionsPeriod, and endDate is then the calculation day. See transactionsPeriod.

A report that omits asOfDate continues to use the current series. Regular income on that report is not taken from a dated detection.

The date must be a real calendar date (yyyy-MM-dd). Anything else is rejected and is not treated as today. Historical affordability is off until Moneyhub enables it. While it is off, a calculate message that includes asOfDate is dead-lettered and no report is produced. See Errors and List of configurable features.

A dated detection can request this report for you when affordability after regular transactions is enabled. The requested report uses the same date. See Regular transactions schema.

Result topic

Successful calculation results are published to the affordability result topic. Failed messages are sent to the affordability dead-letter topic; see Errors for the general dead-letter envelope shape.

Result message envelope

The Kafka message value is a JSON object:

FieldTypeOptionalDescription
userIdString (max 36)YesUser identifier from the calculate request (present for user-scoped runs).
accountIdString (max 36)YesAccount identifier from the calculate request (present for account-scoped runs).
fullAffordabilityReportObjectNoThe affordability report (schema below).

At least one of userId or accountId is present, matching the calculate request. The message key is copied from the triggering calculate message when present; otherwise a new UUID is generated.

Report object (fullAffordabilityReport)

The report analyses up to 12 months of transaction history across accounts in scope for the request. transactionsPeriod records the window actually used. That window ends on asOfDate when the request includes one, and otherwise on the calculation day. Amounts are plain numbers in major currency units (for example, pounds rather than pence).

Missing sections may be omitted when they are not produced for the request. A successful report always includes transactionsPeriod.

FieldTypeOptionalDescription
transactionsPeriodObjectNoDates the report covers.
incomeObjectYesIncome analysis, including regular income detection.
expenditureObjectYesExpenditure medians by affordability category.
transfersObjectYesNet and outgoing transfer medians.
gamblingObjectYesGambling outflow median.
loansObjectYesOpened loan details and repayment median.
returnedPaymentsObjectYesReturned or reversed direct-debit activity.
overdraftsObjectYesOverdraft usage per account.

transactionsPeriod

endDate (yyyy-MM-dd) is the day the report applies to. When the calculate request includes asOfDate, endDate is that date. When the request omits asOfDate, endDate is the calculation day. endDate is not the time the result message was produced.

startDate (yyyy-MM-dd) is the later of the earliest transaction and 12 months before endDate.

FieldTypeOptionalDescription
startDateString (max 10)NoStart of the window used (yyyy-MM-dd).
endDateString (max 10)NoDay the report applies to (yyyy-MM-dd).
periodObjectNoLength of that window.

transactionsPeriod.period

FieldTypeOptionalDescription
monthsIntegerNoLength of the window in months.

income

FieldTypeOptionalDescription
medianNumberYesMedian monthly income (excluding transfers).
medianIncludingTransfersNumberYesMedian monthly income including transfers.
amountConsistencyNumberYesConsistency score for income amounts (0–1).
categoriesUsedArray<Object>YesIncome category IDs referenced in the analysis.
longevityObjectYesHow long income has been observed.
primaryIncomeAccountObjectYesAccount receiving the primary income stream.
regularIncomeObjectYesWhether regular income was detected, plus predicted monthly amount.

income.categoriesUsed[]

FieldTypeOptionalDescription
uidString (max 3)NoLevel 2 category ID.
keyStringNoAffordability category key.

income.longevity

FieldTypeOptionalDescription
consecutiveIncomeObjectYesLongest run of consecutive income months.
incomePeriodLengthObjectYesTotal span of observed income.

Each longevity sub-object has a months integer field.

income.primaryIncomeAccount

FieldTypeOptionalDescription
uidString (max 36)NoAccount identifier.

income.regularIncome

FieldTypeOptionalDescription
isDetectedBooleanYesWhether regular income streams were used in monthly predictions.
predictedObjectYesAggregated regular income prediction.

income.regularIncome.predicted.monthly

FieldTypeOptionalDescription
amountNumberNoPredicted total monthly regular income.

expenditure

Each category object contains a median amount (negative for outflows).

FieldTypeOptionalDescription
cashWithdrawalsObjectYesATM and cash-withdrawal spending.
discretionaryObjectYesDiscretionary spending.
fixedObjectYesFixed commitments such as rent and loan repayments.
flexibleObjectYesFlexible non-discretionary spending.
nonDiscretionaryObjectYesEssential non-discretionary spending.

Expenditure category object

FieldTypeOptionalDescription
medianNumberNoMedian monthly spend (negative for outflows).

transfers

FieldTypeOptionalDescription
medianNumberYesMedian net monthly transfer amount.
outObjectYesOutgoing transfers.

transfers.out

FieldTypeOptionalDescription
medianNumberNoMedian monthly outgoing transfers (negative).

gambling

FieldTypeOptionalDescription
outObjectYesGambling stakes.

gambling.out

FieldTypeOptionalDescription
medianNumberNoMedian monthly gambling spend (negative).

loans

FieldTypeOptionalDescription
openedObjectYesRecently opened loans by type and period.
repaymentsObjectYesLoan repayment median.

loans.opened.details[]

One entry per loan type and loan window (30, 90, 180, and 365 days).

FieldTypeOptionalDescription
typeStringNoLoan type: dca/dmp, bnpl, unsecured, hcstc, secured, or unknown.
countIntegerNoNumber of loans opened in the period.
periodObjectNoLoan window (unit: days; value: 30, 90, 180, or 365).
totalObjectNoAggregated amounts (borrowed, repaid) in major currency units for loans opened in the period.

loans.repayments

FieldTypeOptionalDescription
medianNumberNoMedian monthly loan repayments (negative).

returnedPayments

FieldTypeOptionalDescription
countIntegerYesTotal number of returned-payment transactions in the analysis period.
periodObjectYesLength of the returned-payments analysis (months).
detailsArray<Object>YesReturned-payment counts grouped by category and period.

returnedPayments.details[]

One entry per affordability category and period (30, 90, 180, and 365 days).

FieldTypeOptionalDescription
categoryStringNoAffordability category key for the returned payments.
countIntegerNoNumber of returned payments in the period.
periodObjectNoRolling period (unit: days; value: 30, 90, 180, or 365).
totalObjectNoAggregated returned amount (returned) in major currency units.

overdrafts.usage.details[]

FieldTypeOptionalDescription
accountUidString (max 36)NoAccount identifier.
daysInOverdraftIntegerNoTotal days spent in overdraft in the last 90 days.
daysInUnarrangedOverdraftIntegerYesDays in unarranged overdraft in the last 90 days.
earliestBalanceDateStringNoEarliest balance date available for overdraft analysis (ISO 8601).
periodsInOverdraftArray<Object>NoContiguous overdraft periods in the last 90 days.

overdrafts.usage.details[].periodsInOverdraft[]

FieldTypeOptionalDescription
startString (max 10)NoPeriod start date (yyyy-MM-dd).
endString (max 10)NoPeriod end date (yyyy-MM-dd).
daysIntegerNoNumber of days in the overdraft period.
minimumBalanceNumberNoLowest balance reached during the period.
unarrangedOverdraftObjectYesUnarranged overdraft sub-periods within this period.

unarrangedOverdraft

FieldTypeOptionalDescription
daysIntegerNoTotal unarranged overdraft days in the parent period.
periodsArray<Object>NoContiguous unarranged overdraft sub-periods.

Each periods[] element has start, end, days, and minimumBalance fields (plain-number balances).

Example (result message body)

{
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "fullAffordabilityReport": {
    "transactionsPeriod": {
      "startDate": "2025-04-01",
      "endDate": "2026-04-01",
      "period": {
        "months": 12
      }
    },
    "income": {
      "median": 2403.74,
      "medianIncludingTransfers": 2403.74,
      "amountConsistency": 0.99,
      "categoriesUsed": [
        {
          "uid": "27",
          "key": "deposits"
        }
      ],
      "longevity": {
        "consecutiveIncome": {
          "months": 12
        },
        "incomePeriodLength": {
          "months": 12
        }
      },
      "primaryIncomeAccount": {
        "uid": "b874c5fe-3dc1-4752-a93f-26b99320430c"
      },
      "regularIncome": {
        "isDetected": true,
        "predicted": {
          "monthly": {
            "amount": 2403.74
          }
        }
      }
    },
    "expenditure": {
      "cashWithdrawals": {
        "median": -161.32
      },
      "discretionary": {
        "median": -516.77
      },
      "fixed": {
        "median": -1455.11
      },
      "flexible": {
        "median": -808.26
      },
      "nonDiscretionary": {
        "median": -1748.96
      }
    },
    "transfers": {
      "median": -51.22,
      "out": {
        "median": -203.25
      }
    },
    "gambling": {
      "out": {
        "median": -100.26
      }
    },
    "loans": {
      "opened": {
        "details": []
      },
      "repayments": {
        "median": -283.35
      }
    },
    "returnedPayments": {
      "count": 0,
      "period": {
        "months": 11
      },
      "details": []
    },
    "overdrafts": {
      "usage": {
        "details": []
      }
    }
  }
}

Did this page help you?