Mono-Parser
Parser
Get Started

Built for Nigerian Fintechs

Contents
Table of Contents

Integration Tutorial

A complete integration walkthrough from first API call to receiving a scored decision — covering every step your software needs to handle.

Prerequisites: You need an active API key and webhook secret. Find both in your dashboard under Settings. All requests require the api key for authentication. Remember to save your webhook URL too in the dashboard.

Full Flow — 4 API calls, 4 webhooks

  1. 1POST/initiateCreate applicant + application, get widget URL
  2. 2WidgetApplicant links their bank account in-browser
  3. 3POST/link-accountLink another account, this endpoint is optional but used to link more accounts per application if needed.
  4. 4Webhookaccount.linkedAccount linked; enrichment starts automatically
  5. 5Webhookaccount.enrichment_readyBank data is processed fires a webhook for each account linked
  6. 6POST/finalize-linkingSignal you are done linking accounts
  7. 7Webhookapplication.ready_for_analysisAll accounts enriched; ready to analyze
  8. 8POST/analyzeProcess the creit loan applications
  9. 9Webhookapplication.decisionScored decision delivered to your webhook URL
1

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.

POST/api/applications/initiate
curl
curl -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"
  }'
json
{
  "applicationId": "357ab3ce-55ce-4f73-82c9-dab3136c7885",
  "applicantId":   "54cbd45f-bf8e-4add-8d0c-adb5efe705c1",
  "widgetUrl":     "https://connect.withmono.com/?key=...&reference=...",
  "status":        "PENDING_LINKING"
}
Store 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.
2

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.

Do not poll or wait in your API layer. The rest of the flow is entirely event-driven via webhooks. Return a "pending" state to your user and update them when you receive the decision.
3

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

json
{
  "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.

4

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.

json
{
  "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"
}
Do not call /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.
5

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.

POST/api/applications/:id/finalize-linking
curl
curl -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"
json
{
  "applicationId": "357ab3ce-55ce-4f73-82c9-dab3136c7885",
  "status":        "LINKED",
  "message":       "Linking finalized. Analysis will be available once all accounts are enriched."
}
6

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.

json
{
  "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"
}
7

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.

POST/api/applications/:id/analyze
curl
curl -X POST https://api.mono-parser.shop/api/applications/357ab3ce-55ce-4f73-82c9-dab3136c7885/analyze \
  -H "x-api-key: mp_live_your_secret_key"
json
{
  "applicationId": "357ab3ce-55ce-4f73-82c9-dab3136c7885",
  "status":        "PROCESSING",
  "message":       "Analysis queued."
}
Only call this after receiving application.ready_for_analysis. Calling it earlier will return a 400 error.
8

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.

json
{
  "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.

account.enrichment_failed

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.

json
{
  "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.

application.failed

A terminal error occurred during analysis — no enriched accounts were available, or the scoring service returned an unrecoverable error. The application status is FAILED.

json
{
  "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.

application.abandoned

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.
json
{
  "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.

What's next