Fetching your Income Report
Fetch and interpret the income report: income streams, expenses, disposable and discretionary income.
Authorization
Use the access_token from step 2 and the id from step 1 (or flow_id from the callback).
Fetch the report
GET https://api.finx-s.qwist.cloud/rest/income-reports/{id}
Authorization: Bearer {access_token}
Response:
{
"period": 12,
"income": {
"summary": {
"total": 34200.00,
"low": 2750.00,
"high": 3100.00,
"average": 2850.00,
"count": 12
},
"streams": [
{
"category": { "id": 2, "name": "Salary" },
"first_booked_on": "2024-06-28",
"last_booked_on": "2025-05-30"
}
],
"primary": {
"category": { "id": 2, "name": "Salary" },
"first_booked_on": "2024-06-28",
"last_booked_on": "2025-05-30",
"payment_partner": "Acme GmbH"
}
},
"regular_expenses": [
{
"summary": {
"total": 9600.00,
"low": 800.00,
"high": 800.00,
"average": 800.00,
"count": 12
},
"streams": [
{
"category": { "id": 15, "name": "Rent" },
"first_booked_on": "2024-07-01",
"last_booked_on": "2025-06-01"
}
]
},
{
"summary": {
"total": 1556.40,
"low": 129.70,
"high": 129.70,
"average": 129.70,
"count": 12
},
"streams": [
{
"category": { "id": 42, "name": "Subscriptions" },
"first_booked_on": "2024-07-10",
"last_booked_on": "2025-06-10"
}
]
}
],
"variable_expenses": [
{
"summary": {
"total": 4320.80,
"low": 190.40,
"high": 520.00,
"average": 360.07,
"count": 12
},
"streams": [
{
"category": { "id": 31, "name": "Groceries" },
"first_booked_on": "2024-07-03",
"last_booked_on": "2025-05-29"
}
]
}
],
"disposable_income": {
"total": 23043.60,
"low": 1820.30,
"high": 2170.30,
"average": 1920.30,
"ratio": 0.67
},
"discretionary_income": {
"total": 18722.80,
"low": 1460.23,
"high": 1810.23,
"average": 1560.23,
"ratio": 0.55
}
}How to use this report
The income report gives you the raw numbers. What follows is how to turn them into a lending or affordability decision. These are common-sense thresholds - calibrate them to your product and risk appetite.
Income: stability signals
| Signal | Strong | Needs review | Flag |
|---|---|---|---|
| Income streams identified | 1+ regular stream | Variable income only | No income detected |
| Primary income category | Salary / Pension | Freelance / irregular | Not identifiable |
income.summary.low vs average | Low ≥ 80% of average | 60–80% | < 60% (high variance) |
| Months covered in period | Full period | Gaps of 1–2 months | Gaps > 2 months |
A large gap between low and high monthly income signals an irregular earner. For fixed loan repayments, underwrite to income.summary.low, not average.
Affordability: key ratios
| Signal | Green | Amber | Red |
|---|---|---|---|
disposable_income.ratio | > 0.30 | 0.15–0.30 | < 0.15 |
discretionary_income.ratio | > 0.20 | 0.10–0.20 | < 0.10 |
| Proposed repayment ÷ avg income | < 0.30 | 0.30–0.40 | > 0.40 |
disposable_income = income minus regular (fixed) expenses only. This is the most conservative affordability floor - what the user has left after rent, subscriptions, and existing loan payments.
discretionary_income = income minus all expenses including variable. This represents the true monthly surplus, but it's more volatile - use it as a secondary check rather than the primary decision signal.
Underwrite to the floor, not the averageFor any fixed-repayment product (loan, BNPL, subscription), use
disposable_income.lowas your affordability base. Usingaverageflatters borrowers with volatile income.
Understanding the report
Error handling
The nrich API uses standard HTTP status codes and returns a structured error object in the response body.
Error response structure
{
"error": {
"code": 1000,
"group": "client",
"description": "Request body doesn't match input schema.",
"data": {
"redirect_uri": ["Not a valid URL."]
}
}
}| Field | Description |
|---|---|
code | nrich-specific numeric error code. |
group | Error category: client, user, bank, general, or connectivity. |
description | Short human-readable summary. |
data | Field-level validation details (on 400 errors). |
HTTP status codes
| Code | Meaning | What to do |
|---|---|---|
200 OK | Success. | Parse the response body. |
400 Bad Request | Invalid or missing parameters. | Check error.data for the specific field. |
401 Unauthorized | Token missing or expired. | Refresh using your refresh_token and retry. |
403 Forbidden | Valid token, insufficient scope. | Verify your credentials have the required scopes. |
404 Not Found | Resource not found or not yet created. | Confirm the ID is correct; some resources only exist after the user completes the flow. |
423 Resource Locked | Resource temporarily locked during migration. | Retry with exponential backoff (seconds → minutes). |
500 Internal Server Error | Unexpected server error. | Retry with backoff; contact support if it persists. |
Implement 423 retry logic
423can occur after system updates. Your application must handle it with incremental backoff - do not surface it directly to users.
Common nrich error codes
| Code | Group | Description |
|---|---|---|
1000 | client | Invalid request - check data for field details |
1002 | client | Entity not found |
1008 | client | Resource busy - implement retry with backoff |
10000 | user | Login credentials are invalid |
10001 | user | PIN is invalid |
10004 | user | TAN is invalid |
10007 | user | PIN change required at the bank |
20000 | bank | Processing at the bank not possible |
20003 | bank | Bank under maintenance |
30000 | general | Processing at nrich not possible |
30005 | general | Task has expired |
40000 | connectivity | Bank not supported |
