HomeGuidesAPI Reference
Log In
Guides

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

SignalStrongNeeds reviewFlag
Income streams identified1+ regular streamVariable income onlyNo income detected
Primary income categorySalary / PensionFreelance / irregularNot identifiable
income.summary.low vs averageLow ≥ 80% of average60–80%< 60% (high variance)
Months covered in periodFull periodGaps of 1–2 monthsGaps > 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

SignalGreenAmberRed
disposable_income.ratio> 0.300.15–0.30< 0.15
discretionary_income.ratio> 0.200.10–0.20< 0.10
Proposed repayment ÷ avg income< 0.300.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 average

For any fixed-repayment product (loan, BNPL, subscription), use disposable_income.low as your affordability base. Using average flatters borrowers with volatile income.



Understanding the report

income
regular_expenses
variable_expenses
disposable_income and discretionary_income


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."]
    }
  }
}
FieldDescription
codenrich-specific numeric error code.
groupError category: client, user, bank, general, or connectivity.
descriptionShort human-readable summary.
dataField-level validation details (on 400 errors).

HTTP status codes

CodeMeaningWhat to do
200 OKSuccess.Parse the response body.
400 Bad RequestInvalid or missing parameters.Check error.data for the specific field.
401 UnauthorizedToken missing or expired.Refresh using your refresh_token and retry.
403 ForbiddenValid token, insufficient scope.Verify your credentials have the required scopes.
404 Not FoundResource not found or not yet created.Confirm the ID is correct; some resources only exist after the user completes the flow.
423 Resource LockedResource temporarily locked during migration.Retry with exponential backoff (seconds → minutes).
500 Internal Server ErrorUnexpected server error.Retry with backoff; contact support if it persists.
🚧

Implement 423 retry logic

423 can occur after system updates. Your application must handle it with incremental backoff - do not surface it directly to users.

Common nrich error codes
CodeGroupDescription
1000clientInvalid request - check data for field details
1002clientEntity not found
1008clientResource busy - implement retry with backoff
10000userLogin credentials are invalid
10001userPIN is invalid
10004userTAN is invalid
10007userPIN change required at the bank
20000bankProcessing at the bank not possible
20003bankBank under maintenance
30000generalProcessing at nrich not possible
30005generalTask has expired
40000connectivityBank not supported