Connect your lending system to Vistroy
Vistroy tracks borrowers by Ghana Card across every SIM, every network and every lender. One API call before you pay out a loan tells you whether this person defaulted on another number, is borrowing from several lenders at once, or cannot afford the repayment.
This guide is for the technical team at a bank, microfinance company, savings and loans company or digital lender. Every example below can be copied and run as it is against the sandbox.
| Item | Value |
|---|---|
| Base URL | https://vistroy-credit-score-1.onrender.com |
| Format | JSON over HTTPS. Send Content-Type: application/json on every POST. |
| Authentication | Your institution key in the X-API-Key header |
| Phone numbers | Any Ghana format: 0241234567, +233241234567 or 233241234567 |
| Ghana Card | GHA-123456789-0 |
| Money | Ghana cedis (GHS) as numbers, e.g. 1500 or 1500.50 |
Getting access
- Apply for an account. You get your API key straight away. Copy it: it is shown only once.
- Wait for approval. The Vistroy registry team checks your institution. Until then, requests return
403with "waiting for approval". - Build and test your integration with the examples on this page and the test borrowers below.
Send your key on every request:
X-API-Key: vsk_your_institution_key
The lending flow
- Get consent. Send the borrower an SMS code. When they read it back, you receive a
consent_id. - Run the pre-disbursement check with that
consent_id, right before paying out. You getAPPROVE,MANUAL_REVIEWorDECLINE, the safe amount, and the reasons. - Optional: read the borrower's MoMo statement to get their real income, and pass the
statement_idto the check. - Report the loan once it is paid out, and report every status change after that: late, repaid, defaulted.
- Agree a repayment plan if the borrower falls behind, and record each payment.
- Answer disputes when a borrower says a record is wrong.
Good reporting is what makes the network work: every lender that reports defaults, and every lender that checks before lending, is protected by everyone else's data.
1. Borrower consent
You must have the borrower's permission before looking at their file. Vistroy sends an SMS code to their phone. They read the code back to you (or type it into your app), and that code becomes the proof.
Send the code
curl -X POST BASE/api/consents/otp/start \
-H "X-API-Key: vsk_your_institution_key" \
-H "Content-Type: application/json" \
-d '{
"ghana_card_number": "GHA-000000103-0",
"msisdn": "0551110103",
"purpose": "LOAN_APPLICATION",
"valid_days": 30
}'
Purposes: LOAN_APPLICATION, CREDIT_LIMIT_REVIEW, EXISTING_ACCOUNT_REVIEW, FRAUD_PREVENTION. The borrower's SMS names your institution and the purpose.
{
"challenge_id": "OTP-01AF043AA79E",
"sent_to": "055****103",
"expires_in_minutes": 5,
"sms_live": true
}
Confirm the code
curl -X POST BASE/api/consents/otp/verify \
-H "X-API-Key: vsk_your_institution_key" \
-H "Content-Type: application/json" \
-d '{ "challenge_id": "OTP-01AF043AA79E", "code": "581286" }'
{
"consent_id": "CNS-06D33EB3C0CD",
"purpose": "LOAN_APPLICATION",
"channel": "SMS_OTP",
"evidence_reference": "OTP-01AF043AA79E verified by SMS to 055****103",
"expires_at": "2026-10-30T09:00:00+00:00"
}
test_code field instead.Consent collected another way
If the borrower signed a paper form in a branch or agreed in your own app, record it with POST /api/consents and a channel of BRANCH_PAPER, MOBILE_APP, USSD, WEB or VOICE, plus an evidence_reference such as your form number. SMS codes remain the strongest proof.
2. Pre-disbursement check
Call this right before you pay out. It looks at every phone number linked to the Ghana Card, open loans at other lenders, recent applications, shared devices and affordability, and gives one decision.
curl -X POST BASE/api/v1/check \
-H "X-API-Key: vsk_your_institution_key" \
-H "Content-Type: application/json" \
-d '{
"ghana_card_number": "GHA-000000103-0",
"consent_id": "CNS-06D33EB3C0CD",
"msisdn": "0551110103",
"requested_amount": 1000,
"tenor_days": 30,
"avg_monthly_inflow": 2000,
"momo_history_months": 18,
"device_id": "DEV-12345",
"loan_reference": "LN-0003"
}'
| Field | Required | Meaning |
|---|---|---|
ghana_card_number | Yes | The applicant's Ghana Card |
consent_id | Yes | From step 1 |
msisdn | Yes | The phone number applying now |
requested_amount, tenor_days | Yes | Loan size in GHS and repayment period (1 to 365 days) |
avg_monthly_inflow | Recommended | Average monthly mobile money inflow in GHS. Used for affordability. |
momo_history_months | Optional | How long the wallet has been active |
declared_monthly_income | Optional | Used when inflow is not available |
network, device_id | Optional | Network is detected from the number if left out. Device ID catches one phone used for many identities. |
statement_id | Optional | From step 7. Income and history then come from the statement instead of the typed-in figures, and statement warnings are added. |
loan_reference | Recommended | Your own loan ID. Use the same one when you report the loan, so the pilot report can match the check to the outcome. |
The response (shortened):
{
"application_id": "APP-21F9AC8919",
"decision": "MANUAL_REVIEW",
"requested_amount": 1000.0,
"approved_amount": 600.0,
"score": 649,
"risk_band": "Fair (Limited History)",
"flags": [
{ "code": "UNAFFORDABLE", "severity": "MEDIUM",
"message": "Needs about GHS 1,000.00/month; safe capacity is GHS 600.00/month." }
],
"identity": {
"applying_number": "055****103",
"linked_numbers": [ { "number": "055****103", "network": "MTN", "loans": 1,
"unpaid_default": false, "verified": true } ]
},
"exposure": { "open_loans": 0, "lenders_with_open_loans": 0, "outstanding_ghs": 0 },
"affordability": { "monthly_capacity_ghs": 600.0, "affordable_max_ghs": 600.0 },
"verification_details": { "report_id": "...", "signature": "...", "scoring_model_version": "vistroy-score-0.3" }
}
Keep application_id and verification_details with your loan file. The signature proves the report came from Vistroy and was not changed. Anyone can check it with POST /api/reports/verify.
3. Report loans
Report each loan when you pay it out, then send the same loan_reference again whenever its status changes. Sending it again updates the loan, it never creates a duplicate.
curl -X POST BASE/api/loans/submit \
-H "X-API-Key: vsk_your_institution_key" \
-H "Content-Type: application/json" \
-d '{
"ghana_card_number": "GHA-000000103-0",
"loan_reference": "LN-2026-0001",
"loan_amount": 600,
"monthly_payment": 600,
"repayment_status": "ACTIVE",
"msisdn": "0551110103",
"network": "MTN"
}'
| Status | When to send it |
|---|---|
ACTIVE | Loan paid out, repayments on time |
LATE | A repayment is overdue |
DEFAULTED | You consider the loan in default under your policy |
REPAID | Paid in full |
SETTLED | Closed for less than the full balance |
Always include msisdn. The phone number is what lets Vistroy catch a borrower who defaults on one SIM and applies on another. You can also send opened_date (YYYY-MM-DD) if the loan was paid out on an earlier day.
4. Upload a loan book
Send many loans in one request, for example your whole portfolio on the first day, then a weekly update. Upload the CSV text inside JSON. First check it with dry_run: true to see errors row by row, then import.
Downloads a CSV with the right columns: ghana_card_number, loan_reference, loan_amount, monthly_payment, repayment_status, msisdn, network, opened_date. The last three are optional. Dates can be 2026-03-15 or 15/03/2026.
curl -X POST BASE/api/loans/bulk \
-H "X-API-Key: vsk_your_institution_key" \
-H "Content-Type: application/json" \
-d '{
"csv_text": "ghana_card_number,loan_reference,loan_amount,monthly_payment,repayment_status,msisdn\nGHA-000000901-0,LN-0001,1500,300,ACTIVE,0241234567\n",
"dry_run": true
}'
{
"dry_run": true, "imported": 0,
"rows_valid": 1, "rows_with_errors": 0,
"new_loans": 1, "updated_loans": 0, "borrowers": 1, "defaulted": 0,
"errors": []
}
Then send the same request with "dry_run": false. If some rows have errors, the import is refused unless you add "skip_errors": true, which imports only the valid rows. Up to 5,000 rows per request.
5. Repayment plans
When a borrower falls behind, agree a plan instead of writing the loan off. Vistroy tracks each payment, sends SMS reminders, and marks the loan REPAID (or SETTLED if you accepted less) when the plan is finished. Other lenders see that the borrower is on a plan.
curl -X POST BASE/api/repayment-plans \
-H "X-API-Key: vsk_your_institution_key" \
-H "Content-Type: application/json" \
-d '{
"loan_reference": "LN-2026-0001",
"instalments": 3,
"frequency": "MONTHLY",
"first_due_date": "2026-11-01"
}'
Frequencies: WEEKLY, BIWEEKLY, MONTHLY, ONE_OFF. For a settlement, add outstanding_balance and a lower agreed_amount.
curl -X POST BASE/api/repayment-plans/RPL-9D3CEE2562/payments \
-H "X-API-Key: vsk_your_institution_key" \
-H "Content-Type: application/json" \
-d '{ "amount": 200, "channel": "MOMO", "reference": "MP240930.1234.A56789" }'
Use the MoMo transaction ID as reference. The same reference can't be recorded twice, which prevents double counting.
| Endpoint | Use |
|---|---|
GET /api/repayment-plans | All your plans with progress |
GET /api/repayment-plans/{plan_id} | One plan with schedule, payments and reminders |
POST /api/repayment-plans/{plan_id}/remind | Send the borrower an SMS reminder now (once per 24 hours) |
POST /api/repayment-plans/{plan_id}/cancel | Cancel with a reason |
GET /api/collections/summary | Collected, still to collect, recovery rate, overdue payments |
GET /api/reminders | The last 50 SMS reminders sent for your plans |
Overdue loans, repayment permissions and evidence packs
For a loan marked Late or Defaulted with no open plan, you can send an SMS reminder. It goes only to the borrower's own numbers: the number the loan was paid to, and numbers the borrower confirmed with an SMS code. Never to contacts. At most once every 3 days and 4 times in 30 days, and never while a dispute is open.
| Endpoint | Use |
|---|---|
GET /api/loans/{reference}/collections | Reminder preview and whether one can be sent now, past reminders, repayment permissions |
POST /api/loans/{reference}/remind | Send an overdue reminder now |
POST /api/loans/{reference}/mandates | Record a repayment permission the borrower signed: method (DIRECT_DEBIT, MOMO_AUTO_DEBIT, STANDING_ORDER, PAYROLL_DEDUCTION), provider, account_last4, max_amount, frequency, signed_on, evidence_reference |
POST /api/mandates/{mandate_id}/end | End a repayment permission, with a reason |
GET /api/loans/{reference}/evidence-pack | PDF record of the loan for legal use, with a SHA-256 fingerprint (managers and admins) |
6. Disputes
Borrowers can challenge a record from their own page. Disputes about your loans appear for you to answer within 30 days.
curl -X POST BASE/api/disputes/DSP-700AAC1EDB/respond \
-H "X-API-Key: vsk_your_institution_key" \
-H "Content-Type: application/json" \
-d '{
"action": "CORRECT",
"corrected_status": "REPAID",
"clear_default_history": true,
"notes": "Payment confirmed in our ledger on 12 August."
}'
Use "action": "NO_CHANGE" with a clear explanation if the record is correct.
7. Mobile money statements
Upload the borrower's statement exactly as MTN MoMo, Telecel Cash or AT Money sent it, as a PDF or CSV (up to 5 MB). Vistroy reads the transactions and works out real monthly income, leaving out loan payouts from other apps and reversals. It needs the same consent_id as the check.
curl -X POST BASE/api/momo/statements \
-H "X-API-Key: vsk_your_institution_key" \
-H "Content-Type: application/json" \
-d '{
"ghana_card_number": "GHA-000000901-0",
"consent_id": "CNS-06D33EB3C0CD",
"msisdn": "0241234567",
"file_name": "statement.pdf",
"file_base64": "JVBERi0xLjQK..."
}'
The response (shortened):
{
"statement_id": "STM-6AB787C69A",
"account_number": "024****567",
"account_name": "Kwame Mensah Demo",
"period_start": "2026-06-01", "period_end": "2026-09-28", "months_covered": 3.9,
"transactions_read": 105,
"avg_monthly_income_ghs": 3140.82,
"income_stability": 0.95,
"loan_payouts_received": 1, "loan_payouts_ghs": 500.0,
"gambling_ghs": 250.59, "gambling_share_pct": 2.0,
"balance_checks": 104, "balance_breaks": 0,
"monthly": [ { "month": "2026-06", "income": 3088.99, "loan_payouts": 0.0, "money_out": 2139.76 } ],
"flags": [ { "code": "GAMBLING_SPEND", "severity": "LOW", "message": "Some betting payments (GHS 250.59, 2.0% of income)." } ]
}
Then send "statement_id": "STM-6AB787C69A" with the check. Vistroy keeps only this summary, not the individual transactions. The read appears in the borrower's activity log.
Sample files for testing: sample PDF and sample CSV, for the made-up borrower GHA-000000901-0 on 0241234567.
8. Pilot results
See what the checks caught and what happened to the loans you paid out afterwards. Each check is matched to the loan you reported: by loan_reference if you sent one, otherwise the first loan for the same borrower opened within 30 days.
curl BASE/api/pilot/report?days=90 -H "X-API-Key: vsk_your_institution_key"
You get checks run, decisions, loans declined and not paid out (and how many of those borrowers defaulted at another lender later), loan sizes reduced, default rates by decision, the most common warnings, and money recovered through repayment plans. The CSV has one row per check. A printable version is at /report.
9. Your account and keys
Check your approval status, and manage your keys without contacting Vistroy. You can have up to 3 active keys at a time.
curl -X POST BASE/api/account/keys \
-H "X-API-Key: vsk_your_institution_key" \
-H "Content-Type: application/json" \
-d '{ "label": "Production server" }'
- Create a new key and put it in your system.
- Check that it works.
GET /api/accountshows when each key was last used. - Turn off the old key. It stops working straight away. You can't turn off your last active key.
10. Staff logins
People at your company sign in to the dashboard with their own email and password. Your systems keep using the API key. Every action is recorded with the name of the person who did it.
| Role | Can do |
|---|---|
OFFICER | Record consent, read statements, run checks, report loans and update their status, record plan payments |
MANAGER | Everything an officer can, plus answer disputes, create and cancel repayment plans, upload loan books, reopen closed loans, see the pilot report and staff activity |
ADMIN | Everything, plus add and turn off staff, reset passwords and manage API keys |
curl -X POST BASE/api/staff/login \
-H "Content-Type: application/json" \
-d '{ "email": "officer@yourcompany.com", "password": "..." }'
Send the returned token as X-Staff-Token instead of X-API-Key. It lasts 12 hours. After 5 wrong passwords, the email is locked for 15 minutes.
New staff get a temporary password, shown once, and must choose their own when they first sign in. The API key can add the first admin, for lenders who signed up before staff logins existed. A company always keeps at least one admin.
11. List and update loans
Your loans, newest change first, with counts by status, the decision from the matching check, and any repayment plan.
curl -X POST BASE/api/loans/LN-2026-0001/status \
-H "X-API-Key: vsk_your_institution_key" \
-H "Content-Type: application/json" \
-d '{ "status": "LATE", "note": "Missed the October payment" }'
Changes only the status. Reopening a Repaid or Settled loan needs a manager. A loan with an active repayment plan closes through the plan, not here.
12. Borrower data rights
Borrowers can download everything Vistroy holds about them from the My Credit File page: loans, phone numbers, consents, checks, statement summaries and who looked at their file. They can also ask for a correction, deletion, or for Vistroy to stop using their data. The Vistroy team answers within 30 days.
A completed deletion removes statement summaries and device records and withdraws all consents. Loan records are kept, so lenders keep a fair history. After consents are withdrawn, checks return 403 until the borrower gives a new consent.
13. Borrower approves on their phone
After you call /api/consents/otp/start, the borrower can read you the SMS code as before, or open the Vistroy USSD or WhatsApp menu and choose 3 Approve a lender. They have 15 minutes. Check the answer with:
curl BASE/api/consents/otp/OTP-1A2B3C4D5E6F \
-H "X-API-Key: vsk_your_institution_key"
status is WAITING, APPROVED (with the consent to use in your check), DECLINED or EXPIRED. Ask every few seconds while the borrower is with you. The dashboard does this for you.
Decisions and risk flags
| Decision | Meaning |
|---|---|
APPROVE | No serious risk found. Lend up to approved_amount. |
MANUAL_REVIEW | Something needs a person to look at it, or lend only the lower approved_amount. |
DECLINE | At least one high-severity flag. Don't lend. |
| Flag | Severity | What it means |
|---|---|---|
DEFAULT_ON_LINKED_NUMBER | HIGH | An unpaid default exists on another phone number linked to this Ghana Card |
NEW_SIM_AFTER_DEFAULT | HIGH | The applying number first appeared after a default on another number |
LOAN_STACKING | HIGH / MEDIUM | New loans from other lenders in the last 7 days |
UNAFFORDABLE | HIGH / MEDIUM | Repayments would take more than 30% of monthly income |
DEVICE_SHARED_ACROSS_IDENTITIES | HIGH / MEDIUM | The same phone handset is used with several Ghana Cards |
APPLICATION_VELOCITY | MEDIUM | Many applications at other lenders in the last 24 hours |
HIGH_ACTIVE_EXPOSURE | MEDIUM | Many open loans at the same time |
MANY_LINKED_NUMBERS | MEDIUM | An unusually high number of SIMs used for borrowing |
OWNER_REPORTED_FRAUD | HIGH | The Ghana Card owner pressed "This wasn't me" on a fraud alert, or reported identity fraud. Confirm identity in person before paying out. |
NUMBER_SHARED_ACROSS_IDENTITIES | MEDIUM | This phone number was used with another Ghana Card |
BROKEN_REPAYMENT_PLAN | MEDIUM | The borrower broke a repayment plan with a lender |
ON_REPAYMENT_PLAN | LOW | The borrower is paying off an earlier loan through a plan |
THIN_MOMO_HISTORY | LOW | The mobile money wallet is new |
AFFORDABILITY_UNKNOWN | LOW | No income information was sent |
| From a MoMo statement (step 7) | ||
STATEMENT_USED_FOR_OTHER_ID | HIGH | The same statement was already used for a different Ghana Card |
STATEMENT_NUMBER_NOT_LINKED | MEDIUM | The statement is for a number not linked to this Ghana Card |
STATEMENT_BALANCE_MISMATCH | MEDIUM | Balances don't add up between transactions. The statement may have been edited. |
RECENT_INCOME_SPIKE | MEDIUM | Income in the last 30 days is much higher than before, as if money was moved in to look better |
GAMBLING_SPEND | MEDIUM / LOW | Payments to betting companies. MEDIUM when 10% of income or more. |
LOAN_PAYOUTS_FROM_OTHER_APPS | MEDIUM / LOW | Loans received from other apps. MEDIUM when 3 or more in the last 30 days. |
IRREGULAR_INCOME | LOW | Monthly income goes up and down a lot |
SHORT_STATEMENT | LOW | Less than 3 months of transactions |
STATEMENT_OUT_OF_DATE | LOW | The latest transaction is more than 45 days old |
Errors
Errors return a JSON body with a plain-language detail you can show to your staff.
| Code | Meaning | What to do |
|---|---|---|
401 | Missing, wrong, turned-off or deactivated API key, or an expired staff login | Check the X-API-Key header, or sign in again |
403 | Your account is still waiting for approval, your staff role can't do this, or no valid consent for this borrower (missing, expired, withdrawn, or given to another lender) | Get a new consent from the borrower |
404 | Borrower, loan or plan not found in your records | Check the reference |
409 | Conflict, e.g. a payment reference already recorded, or a loan already closed | Don't retry the same request |
410 | The SMS code has expired | Send a new code |
422 | A field is missing or in the wrong format | Read detail and fix the request |
429 | Too many requests, e.g. too many SMS codes to one number | Wait and try again later |
502 / 503 | The SMS provider could not be reached or is not set up | Try again. Contact Vistroy if it continues. |
Test data (sandbox)
These test borrowers exist in the sandbox so you can see each outcome. They are not real people.
| Ghana Card | Apply with | Expected result |
|---|---|---|
GHA-000000101-0 | 0201110101 | DECLINE: unpaid default on another SIM, new SIM after default |
GHA-000000102-0 | 0541110102, inflow 2500 | DECLINE: loan stacking across lenders |
GHA-000000103-0 | 0551110103, amount 3000, inflow 2000 | MANUAL_REVIEW: good history, lend up to GHS 600 |
GHA-000000104-0 | 0241110104 | DECLINE: defaulted loan at another lender, now on a repayment plan (1 of 3 paid) |
Data rules
- Only check a borrower who has given consent, and only for the purpose they agreed to.
- Every check and every loan update appears in the borrower's own activity log, with your institution's name.
- Borrowers get an SMS fraud alert when you check their file (at most one per lender per day) and when a new phone number is linked to their Ghana Card. If they press "This wasn't me", your permissions for that borrower are withdrawn and the file shows
OWNER_REPORTED_FRAUDto every lender until the report is closed. - Phone numbers of other lenders' customers are always masked (
024****104). - Report accurately and on time. Correct a wrong record as soon as you know about it.
- Answer borrower disputes within 30 days.
Questions: contact the Vistroy team. To get a key, apply for an account. The full list of endpoints and fields is in the interactive API reference.