Integration Tutorial
A complete integration walkthrough from first API call to receiving a scored decision — covering every step your software needs to handle.
Full Flow — 4 API calls, 4 webhooks
- 1POST/initiateCreate applicant + application, get widget URL
- 2Widget—Applicant links their bank account in-browser
- 3POST/link-accountLink another account, this endpoint is optional but used to link more accounts per application if needed.
- 4Webhookaccount.linkedAccount linked; enrichment starts automatically
- 5Webhookaccount.enrichment_readyBank data is processed fires a webhook for each account linked
- 6POST/finalize-linkingSignal you are done linking accounts
- 7Webhookapplication.ready_for_analysisAll accounts enriched; ready to analyze
- 8POST/analyzeProcess the creit loan applications
- 9Webhookapplication.decisionScored decision delivered to your webhook URL
Initiate the Application
This single call creates the applicant record, creates the loan application, and returns a Mono Connect widgetUrl you give to your user. Store the applicationId — every subsequent call uses it.
/api/applications/initiatecurl -X POST https://api.mono-parser.shop/api/applications/initiate \
-H "Content-Type: application/json" \
-H "x-api-key: mp_live_your_secret_key" \
-d '{
"firstName": "Olusegun",
"lastName": "Adeyemi",
"email": "olusegun.adeyemi@example.com",
"phone": "08012345678",
"bvn": "22345678901",
"amount": 500000,
"tenor": 12,
"interestRate": 2.0,
"purpose": "Business expansion"
}'{
"applicationId": "357ab3ce-55ce-4f73-82c9-dab3136c7885",
"applicantId": "54cbd45f-bf8e-4add-8d0c-adb5efe705c1",
"widgetUrl": "https://connect.withmono.com/?key=...&reference=...",
"status": "PENDING_LINKING"
}applicationId and applicantId against your user record. The widget URL is single-use and expires — redirect or present it immediately. Do not call it yourself; it is for the applicant's browser.Open the Mono Connect Widget
Redirect the applicant to the widgetUrl, or open it inside a modal. Mono handles the full bank-linking flow. When the applicant completes linking, Mono closes the widget and you will receive the account.linked webhook on your registered URL.
Receive account.linked
After the applicant completes the Mono widget, we fire account.linked to your webhook URL. Enrichment (income analysis, transaction categorisation) begins automatically — you do not need to do anything to trigger it.
Payload
{
"event": "account.linked",
"data": {
"applicationId": "357ab3ce-55ce-4f73-82c9-dab3136c7885",
"applicantId": "54cbd45f-bf8e-4add-8d0c-adb5efe705c1",
"accountId": "dbcc78d5-0719-4344-ac0e-e02a1f865bf0",
"institution": "Standard Chartered",
"accountNumber": "0131883461"
},
"timestamp": "2026-02-20T22:04:14.123Z"
}What to store
Store accountId and institution against the application. You will need applicationId to call finalize-linking once all accounts are ready.
Wait for account.enrichment_ready
This event fires once per account when income analysis and transaction insights have completed. For a single-account applicant you will receive this once. For a multi-account applicant, you will receive it once per account — track them and only call finalize-linking after all accounts have fired this event.
{
"event": "account.enrichment_ready",
"data": {
"accountId": "dbcc78d5-0719-4344-ac0e-e02a1f865bf0",
"monoAccountId": "6998da59bdaef66d5e5f3d0d",
"applicantId": "54cbd45f-bf8e-4add-8d0c-adb5efe705c1",
"applicationId": "357ab3ce-55ce-4f73-82c9-dab3136c7885",
"message": "Account enrichment complete. You may now submit this applicant for loan analysis."
},
"timestamp": "2026-02-20T22:07:55.382Z"
}/analyze here. You must call /finalize-linking first. That call locks the application and triggers the definitive application.ready_for_analysis event which is your signal to analyze.Call finalize-linking
Once all accounts the applicant is linking have fired account.enrichment_ready, call this endpoint. It locks the application from further account additions and tells us to fire application.ready_for_analysis once all enrichment is confirmed complete on our side.
/api/applications/:id/finalize-linkingcurl -X POST https://api.mono-parser.shop/api/applications/357ab3ce-55ce-4f73-82c9-dab3136c7885/finalize-linking \ -H "x-api-key: mp_live_your_secret_key"
{
"applicationId": "357ab3ce-55ce-4f73-82c9-dab3136c7885",
"status": "LINKED",
"message": "Linking finalized. Analysis will be available once all accounts are enriched."
}Receive application.ready_for_analysis
Unlike account.enrichment_ready which fires per account, this fires once per application. It is the definitive signal that all linked accounts are enriched and you are safe to call /analyze.
{
"event": "application.ready_for_analysis",
"data": {
"applicationId": "357ab3ce-55ce-4f73-82c9-dab3136c7885",
"applicantId": "54cbd45f-bf8e-4add-8d0c-adb5efe705c1",
"accountCount": 1,
"message": "All accounts are enriched. You may now submit for analysis."
},
"timestamp": "2026-02-20T22:08:10.441Z"
}Call analyze
Queue the scoring job. The engine reads all enriched data we hold for this application and delivers the decision to your webhook URL. This call returns immediately — the scoring is asynchronous.
/api/applications/:id/analyzecurl -X POST https://api.mono-parser.shop/api/applications/357ab3ce-55ce-4f73-82c9-dab3136c7885/analyze \ -H "x-api-key: mp_live_your_secret_key"
{
"applicationId": "357ab3ce-55ce-4f73-82c9-dab3136c7885",
"status": "PROCESSING",
"message": "Analysis queued."
}application.ready_for_analysis. Calling it earlier will return a 400 error.Receive application.decision
The final scored decision is delivered to your webhook URL. The decision field can be APPROVED, COUNTER_OFFER, REJECTED, or MANUAL_REVIEW.
{
"event": "application.decision",
"data": {
"applicationId": "357ab3ce-55ce-4f73-82c9-dab3136c7885",
"applicantId": "54cbd45f-bf8e-4add-8d0c-adb5efe705c1",
"status": "COMPLETED",
"score": 720,
"decision": {
"score": 720,
"decision": "APPROVED",
"score_band": "LOW_RISK",
"timestamp": "2026-02-20T22:14:35.576Z",
"approval_details": {
"approved_amount": 500000,
"approved_tenor": 12,
"approved_interest": 24.0,
"monthly_payment": 46667
},
"counter_offer": null,
"score_breakdown": {
"total": 720,
"cash_flow_health": 180,
"income_stability": 160,
"debt_service_capacity": 140,
"account_behavior": 140,
"credit_history": 100
},
"explainability": {
"primary_reason": "Strong income consistency and healthy cash flow",
"key_strengths": ["Consistent monthly credits", "Low debt-to-income ratio"],
"key_weaknesses": []
},
"risk_factors": [],
"manual_review_reasons": [],
"eligible_tenors": [6, 12, 18, 24],
"regulatory_compliance": {
"thin_file": false,
"identity_verified": true,
"credit_bureau_checked": true,
"affordability_assessed": true
}
}
},
"timestamp": "2026-02-20T22:14:35.703Z"
}APPROVED / COUNTER_OFFER
Present the approval_details to the user. For a counter-offer, show the counter_offer block instead — it contains the revised amount, tenor, monthly payment, and a plain-language reason.
REJECTED
The explainability.primary_reason and risk_factors give you a plain-language explanation safe to relay to the applicant.
MANUAL_REVIEW
The application lands in your loan officer queue. manual_review_reasons explains why automatic scoring could not issue a final decision. Your team reviews it from the dashboard.
Edge cases
Three additional webhook events can interrupt the happy path. Handle all of them or your users will end up in silent dead ends.
Enrichment stayed in PENDING for more than 20 minutes. Our cleanup job marks the account FAILED and fires this event. The applicant needs to re-link.
{
"event": "account.enrichment_failed",
"data": {
"accountId": "dbcc78d5-...",
"applicantId": "54cbd45f-...",
"reason": "enrichment_timeout",
"message": "Ask the applicant to re-link their bank account."
}
}Action: Call POST /api/applications/:id/link-account to generate a fresh widget URL and restart enrichment for that account.
A terminal error occurred during analysis — no enriched accounts were available, or the scoring service returned an unrecoverable error. The application status is FAILED.
{
"event": "application.failed",
"data": {
"applicationId": "357ab3ce-...",
"applicantId": "54cbd45f-...",
"status": "FAILED",
"reason": "No accounts with completed enrichment available for analysis"
}
}Action: Call /initiate to start a new application. A failed application cannot be retried.
Fired by the cleanup system when an application goes cold. Two possible reasons:
no_linkApplicant did not open or complete the Mono widget within 24 hours of initiation.no_analyzeAccount was linked but /analyze was never called within 7 days. Bank data has been scrubbed from our systems.{
"event": "application.abandoned",
"data": {
"applicationId": "357ab3ce-...",
"applicantId": "54cbd45f-...",
"reason": "no_link",
"message": "Applicant did not link a bank account within 24 hours."
}
}Action: For both reasons, call /initiate to create a fresh application. The applicant will need to re-link.