Implementation Guide
Create a risk report flow (one-time or ongoing), exchange the authorization code, and fetch the report.
Getting Started
Three API calls to generate a risk report. Risk Insights supports both one-time and ongoing access - choose the flow that matches your use case.
sequenceDiagram
participant Y as Your server
participant N as nrich API
participant U as User (Widget)
Y->>N: POST /onetime/risk-report (or /ongoing/risk-report)
N-->>Y: { location: "https://widget...", id: "abc123" }
Y->>U: Redirect user to location URL
U->>U: Bank selection + SCA
U->>Y: Redirect to redirect_uri?code=...&flow_id=abc123
Y->>N: POST /auth/token (grant_type: authorization_code)
N-->>Y: { access_token, refresh_token }
Y->>N: GET /rest/risk-reports/abc123
N-->>Y: Risk report JSON
Create a Widget link for a risk report
Exchange the authorization code for an access token
Fetch the risk report
1. Create the Widget link
Use for a single risk snapshot (e.g. at loan origination).
POST https://api.finx-s.qwist.cloud/onetime/risk-report
{
"redirect_uri": "https://my-app.example.com/callback",
"language": "de",
"reporting_period": 12,
"name": "Maria Müller",
"accounts": [
{ "id": "DE89370400440532013000" }
],
"account_types": ["Giro account"],
"allow_multi_selection": false
}Parameters:
| Parameter | Required for ongoing | Description |
|---|---|---|
redirect_uri | ✅ | Where the user is returned after the flow. |
user_id | ✅ (ongoing only) | Your unique identifier for this user. Allows multiple bank connections per user. |
name | The user's claimed name. If provided, the report includes a name-match verification result. If omitted, no name verification is performed. | |
reporting_period | Months of transaction history to analyse. API minimum: 4. Recommended for underwriting: 12. Default: 12. Cannot be set at the same time as sync_period. | |
accounts | Pre-fill the user's IBAN. Skips bank selection. | |
account_types | Restrict to specific account types. Skips the type selection screen. | |
allow_multi_selection | false restricts to one account - recommended for clean risk analysis. | |
save_secrets | Ongoing only. false (default): user chooses. true: always save credentials (enables autosync). | |
language | Widget UI language: de, en, es, fr, ar. Default: de. |
Include the namePassing
nameadds account holder verification to the report at no extra step for the user. Without it, theverification_resultfield is absent from the report entirely.
Single account for cleaner analysisSet
allow_multi_selection: falseand pass the user's primary Giro account IBAN. This ensures income and expense aggregations are tied to one account, making the report easier to interpret.
Response:
{
"location": "https://widget.qwist.cloud/?token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"id": "b7c4e2a9-3f8d-4a1b-9e5c-2d6f8a0b4e7c"
}
Store theidThis is the report ID you'll need in step 3. It is also returned as
flow_idin the callback. If you capture it from the callback, you don't need to store it separately - but it's good practice to store it for support queries.
2. Exchange the authorization code
2. Exchange the authorization code
After the user completes the Widget flow, they are redirected to your redirect_uri with:
| Parameter | Description |
|---|---|
code | Authorization code valid for 1 hour. Present only on success. |
flow_id | Unique identifier for this flow. Matches the id from step 1. |
success | true if the user completed the flow. |
abort | true if the user cancelled. |
state | Your original state value, echoed back for session correlation. |
Example callback URL:
https://my-app.example.com/callback
?code=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
&flow_id=3ca31c37-986a-454e-ad64-8e97143c86bc
&success=true
&abort=false
&state=user_42_session_9f3a
If abort is trueThe user cancelled - no
codeis returned. Show a dismissal state and let the user retry. Do not attempt to exchange a missing code.
Exchange the code for tokens
POST https://api.finx-s.qwist.cloud/auth/token
// Request
{
"grant_type": "authorization_code",
"code": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"redirect_uri": "https://my-app.example.com/callback"
}// Response
{
"access_token": "AoFmNJLDTW8jQtGSJ1iZeeoLiwNZ2ihz3iiCHGpuvE439nppuY...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "RTfI2WNyK78NozupDH9ai8GPRbjjdVsXPPt...",
"scope": "accounts=ro balance=ro transactions=ro offline"
}Use the access_token as a Bearer token in the Authorization header for all subsequent requests. Store both tokens - you will need the refresh_token to keep access alive without re-asking the user.
| Token | Lifetime | Notes |
|---|---|---|
| Authorization code | 1 hour | Single use |
| Access token | 1 hour | Authorization: Bearer {token} |
| Refresh token | 90 days | Each use returns a new token - replace it immediately |
Token storageToken lengths vary. Allocate at least 2048 bytes per token field in your database - do not use fixed-size columns.
Refreshing the access token
Access tokens expire after 1 hour. For ongoing access, refresh server-side before expiry rather than waiting for a 401.
POST https://api.finx-s.qwist.cloud/auth/token
// Request
{
"grant_type": "refresh_token",
"refresh_token": "RTfI2WNyK78NozupDH9ai8GPRbjjdVsXPPt..."
}// Response
{
"access_token": "BpGnOKMEUX9kRuHTK2jZfpoMjxOa3jhQtjjDIHqwvF540oqqvZ...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "SUgJ3XOzL89PazvrEI0bj9HQSckkwWtYQQu...",
"scope": "accounts=ro balance=ro transactions=ro offline"
}
Replace the refresh token immediatelyEach call invalidates the previous
refresh_tokenand returns a new one. Persist the new token before using the newaccess_token- using the old refresh token will fail with401.
3. Fetch the risk report
You need the id from step 1 (or flow_id from the callback) and the access_token. See Fetching your Risk Report.
