MyItura API

MyItura Telemedicine API

Appointment Guide - Complete API Reference for Healthcare Integration

The MyItura Telemedicine API enables seamless integration of audio and video appointments into your healthcare application. This powerful API facilitates remote consultations between patients and healthcare providers, enhancing accessibility and convenience in medical care.

Key Features

  • Audio/Video Appointments: Conduct secure, high-quality telehealth consultations
  • Call Summaries: Automatically generate comprehensive summaries of each appointment
    • Clinical Summary: Detailed medical summary for healthcare providers (transcriptionSummary)
    • Patient Summary: Simplified, patient-friendly summary (patientSummary)
  • Doctor's Notes: Allow physicians to create and manage digital clinical notes
  • E-Prescriptions: Enable secure electronic prescription creation and management
  • Call Recordings: Access audio recordings of completed appointments
  • AI-Powered Transcription: Automatic transcription and summarization of appointment sessions

Whether you're building a patient portal, a clinic management system, or a comprehensive healthcare platform, the MyItura Telemedicine API provides the tools you need to incorporate robust telemedicine capabilities into your solution.

Base URL: https://URL/api/v1/appointment

Appointment Flow

Telemedicine Appointment Flow

Authentication

All API requests require authentication using your API Keys.

Required Headers

HeaderDescriptionExample
x-api-public-keyYour public API keypk_live_abc123
x-api-secret-keyYour secret API keysk_live_xyz789
Security Note: Never share your secret key or commit it to version control.

Getting Your API Keys

  1. Login to your organization dashboard at MyItura Portal
  2. Navigate to Settings → API Keys
  3. Click "Generate New API Keys"
  4. Copy both your Public Key and Secret Key
  5. Store them securely (e.g., environment variables)
💡 Tip: Your public key starts with pk_ and your secret key starts with sk_

Create Appointment

The Create Appointment endpoint allows you to schedule a new telemedicine appointment in the MyItura system. This endpoint enables the creation of audio/video consultations between patients and healthcare providers. When called, it sets up the appointment with essential details such as the participants information, the scheduled start time and the scheduled end time. Upon successful creation, the system returns a unique appointment ID and a URL for each participant to join the virtual meeting.

POST/appointment/create

Code Example

curl -X POST https://api.myitura.com/api/v1/appointment/create \
  -H "x-api-public-key: pk_live_abc123def456" \
  -H "x-api-secret-key: sk_live_xyz789uvw012" \
  -H "Content-Type: application/json" \
  -d '{
    "appointmentStartTime": "2025-11-10T09:00:00.000Z",
    "appointmentEndTime": "2025-11-10T09:30:00.000Z",
    "participants": [
      {
        "firstName": "John",
        "lastName": "Doe",
        "email": "john.doe@example.com",
        "phoneNumber": "+1234567890",
        "role": "patient"
      },
      {
        "firstName": "Dr. Jane",
        "lastName": "Smith",
        "email": "jane.smith@hospital.com",
        "phoneNumber": "+1987654321",
        "role": "doctor"
      }
    ]
  }'

Response

Response
{
  "success": true,
  "appointmentId": "507f1f77bcf86cd799439011",
  "meetingId": "meeting-uuid-here",
  "appointmentStartTime": "2025-11-10T09:00:00.000Z",
  "appointmentEndTime": "2025-11-10T09:30:00.000Z",
  "appointmentStatus": "scheduled",
  "organisationId": "org-mongo-id",
  "authOrgId": "d41170fb-179c-4339-9c79-e565eacd22d8",
  "participants": [
    {
      "participant": {
        "firstName": "John",
        "lastName": "Doe",
        "email": "john.doe@example.com",
        "phoneNumber": "+1234567890",
        "role": "patient",
        "communicationsUserClientID": "comm-user-id-1"
      },
      "participantKey": "unique-key-1",
      "participantMeetingLink": "https://meeting-link-1",
      "liveliness": "active"
    },
    {
      "participant": {
        "firstName": "Dr. Jane",
        "lastName": "Smith",
        "email": "jane.smith@hospital.com",
        "phoneNumber": "+1987654321",
        "role": "doctor",
        "communicationsUserClientID": "comm-user-id-2"
      },
      "participantKey": "unique-key-2",
      "participantMeetingLink": "https://meeting-link-2",
      "liveliness": "active"
    }
  ]
}

Get Single Appointment

The Get Appointment endpoint allows you to retrieve information about a scheduled appointment. Using this endpoint you will be able to view the created audio/video consultations.

GET/appointment?appointmentId={id}

Code Example

curl -X GET "https://api.myitura.com/api/v1/appointment?appointmentId=507f1f77bcf86cd799439011" \
  -H "x-api-public-key: pk_live_abc123def456" \
  -H "x-api-secret-key: sk_live_xyz789uvw012"

Response

Response
{
  "_id": "507f1f77bcf86cd799439011",
  "meetingId": "meeting-uuid-here",
  "appointmentStartTime": "2025-11-10T09:00:00.000Z",
  "appointmentEndTime": "2025-11-10T09:30:00.000Z",
  "appointmentStatus": "completed",
  "organisationId": "org-mongo-id",
  "authOrgId": "d41170fb-179c-4339-9c79-e565eacd22d8",
  "audioRecordingURLs": [
    "https://storage.myitura.com/recordings/appointment-xyz-part1.mp3",
    "https://storage.myitura.com/recordings/appointment-xyz-part2.mp3"
  ],
  "transcriptionSummary": "Clinical Summary:\n- Chief Complaint: Patient reports persistent headache for 3 days\n- Vital Signs: BP 120/80, Temp 98.6°F\n- Assessment: Tension headache, likely stress-related\n- Treatment Plan: Prescribed ibuprofen 400mg, recommended stress management techniques",
  "patientSummary": "Your Visit Summary:\nYou saw the doctor today about your headaches. The doctor thinks they are tension headaches from stress. You were given medicine (ibuprofen 400mg) to help with the pain. The doctor also suggested trying relaxation exercises to help reduce stress.",
  "participants": [
    {
      "participant": {
        "firstName": "John",
        "lastName": "Doe",
        "email": "john.doe@example.com",
        "role": "patient"
      },
      "participantKey": "unique-key-1",
      "participantMeetingLink": "https://meeting-link-1"
    },
    {
      "participant": {
        "firstName": "Dr. Jane",
        "lastName": "Smith",
        "email": "jane.smith@hospital.com",
        "role": "doctor"
      },
      "participantKey": "unique-key-2",
      "participantMeetingLink": "https://meeting-link-2"
    }
  ],
  "prescriptions": [
    {
      "prescribedDrugs": [
        {
          "drugName": "Ibuprofen",
          "dosage": "400mg",
          "frequency": "Every 6 hours as needed",
          "duration": "7 days"
        }
      ],
      "additionalNotes": "Take with food to avoid stomach upset",
      "createdAt": "2025-11-10T09:25:00.000Z"
    }
  ],
  "doctorNotes": [
    {
      "symptomsandIllnesses": "Persistent headache x3 days, photosensitivity",
      "diagnosis": "Tension headache",
      "treatmentandRecommendation": "Ibuprofen 400mg PRN, stress management, follow-up in 1 week if symptoms persist",
      "doctorSignature": "Dr. Jane Smith, MD"
    }
  ],
  "prescriptionUrl": "https://storage.myitura.com/prescriptions/prescription-xyz.pdf",
  "createdAt": "2025-11-07T10:00:00.000Z",
  "updatedAt": "2025-11-10T09:30:00.000Z"
}

Response Field Descriptions

FieldTypeDescription
transcriptionSummarystringClinical summary for healthcare providers - detailed medical information
patientSummarystringSimplified summary for patients - easy-to-understand visit summary
audioRecordingURLsarrayArray of URLs to download call recordings (available after appointment completion, may contain multiple files if session was split)
prescriptionsarrayList of prescriptions issued during the appointment
doctorNotesarrayClinical notes documented by the healthcare provider
prescriptionUrlstringURL to downloadable PDF prescription document

Get All Appointments for Organization

GET/appointment/organisation?limit=10&page=2

Query Parameters

ParameterTypeRequiredDefaultDescription
pagenumberNo1Page number for pagination
limitnumberNo10Number of items per page
appointmentStatusstringNo-Filter by status (e.g., "scheduled", "completed", "cancelled")
startDatestringNo-Filter from date (YYYY-MM-DD format)
endDatestringNo-Filter to date (YYYY-MM-DD format)

Code Example

curl -X GET "https://api.myitura.com/api/v1/appointment/organisation?limit=20&page=1&appointmentStatus=scheduled" \
  -H "x-api-public-key: pk_live_abc123def456" \
  -H "x-api-secret-key: sk_live_xyz789uvw012"

Response

Response
{
  "appointments": [
    {
      "_id": "507f1f77bcf86cd799439011",
      "meetingId": "meeting-uuid-1",
      "appointmentStartTime": "2025-11-10T09:00:00.000Z",
      "appointmentEndTime": "2025-11-10T09:30:00.000Z",
      "appointmentStatus": "scheduled",
      "organisationId": "org-mongo-id",
      "authOrgId": "d41170fb-179c-4339-9c79-e565eacd22d8",
      "participants": [
        {
          "participant": {
            "firstName": "John",
            "lastName": "Doe",
            "role": "patient"
          }
        }
      ],
      "createdAt": "2025-11-07T10:00:00.000Z"
    },
    {
      "_id": "507f1f77bcf86cd799439012",
      "meetingId": "meeting-uuid-2",
      "appointmentStartTime": "2025-11-10T10:00:00.000Z",
      "appointmentEndTime": "2025-11-10T10:30:00.000Z",
      "appointmentStatus": "completed",
      "organisationId": "org-mongo-id",
      "authOrgId": "d41170fb-179c-4339-9c79-e565eacd22d8",
      "participants": [
        {
          "participant": {
            "firstName": "Jane",
            "lastName": "Smith",
            "role": "doctor"
          }
        }
      ],
      "createdAt": "2025-11-06T10:00:00.000Z"
    }
  ],
  "total": 45,
  "page": 2,
  "limit": 10,
  "totalPages": 5
}

Cancel Appointment

Cancel a scheduled appointment. You can only cancel appointments that are scheduled to start more than 5 minutes in the future.

PUT/appointment/cancel?appointmentId={id}
Important: Appointments that have already started or are starting within 5 minutes cannot be cancelled.

Code Example

curl -X PUT "https://api.myitura.com/api/v1/appointment/cancel?appointmentId=507f1f77bcf86cd799439011" \
  -H "x-api-public-key: pk_live_abc123def456" \
  -H "x-api-secret-key: sk_live_xyz789uvw012"

Success Response

Response
{
  "success": true,
  "message": "Appointment cancelled successfully"
}

Error Response

Response
{
  "statusCode": 400,
  "message": "Cannot cancel appointments that have already started or are starting within 5 minutes"
}

Reschedule Appointment (Modify Time)

Modify the start and end time of a scheduled appointment.

PUT/appointment/modify-time?appointmentId={id}
Note: If the new appointment time is in the past, you will receive an error suggesting to cancel the appointment instead.

Code Example

curl -X PUT "https://api.myitura.com/api/v1/appointment/modify-time?appointmentId=507f1f77bcf86cd799439011" \
  -H "x-api-public-key: pk_live_abc123def456" \
  -H "x-api-secret-key: sk_live_xyz789uvw012" \
  -H "Content-Type: application/json" \
  -d '{
    "startTime": "2025-11-10T10:00:00.000Z",
    "endTime": "2025-11-10T10:30:00.000Z"
  }'

Success Response

Response
{
  "success": true,
  "message": "Appointment time modified successfully",
  "appointment": {
    "success": true,
    "message": "Appointment time modified successfully",
    "updatedAppointment": {
      "_id": "507f1f77bcf86cd799439011",
      "appointmentStartTime": "2025-11-10T10:00:00.000Z",
      "appointmentEndTime": "2025-11-10T10:30:00.000Z",
      "appointmentStatus": "CONFIRMED",
      "participants": [...]
    }
  }
}

Error Response (Past Time)

Response
{
  "success": false,
  "message": "Cannot modify appointment to a past time. Please cancel the appointment instead."
}

Error Handling

HTTP Status Codes

CodeStatusDescription
200OKRequest succeeded
400Bad RequestInvalid request parameters or attempting to cancel/modify past appointments
401UnauthorizedInvalid or missing API keys
404Not FoundAppointment or resource not found

Important Notes

Authentication

All requests require valid API keys (Public Key and Secret Key) in headers

Date Format

  • Appointment times use ISO 8601 format (e.g., 2025-11-10T09:00:00.000Z)
  • Date filters use YYYY-MM-DD format (e.g., 2025-11-10)

Pagination

Default is 10 items per page starting at page 1

Appointment Status

Common values are "scheduled", "completed", "cancelled", "in-progress"

Post-Appointment Data Availability

  • audioRecordingURLs: Available approximately 30 minutes after appointment ends (may contain multiple recordings if session was split)
  • transcriptionSummary: Available after audio transcription completes (typically 35-45 minutes post-appointment)
  • patientSummary: Available after AI summarization completes (typically 35-45 minutes post-appointment)
  • prescriptions: Available immediately after doctor submits prescription during or after appointment
  • doctorNotes: Available immediately after doctor submits notes during or after appointment

AI-Generated Summaries

  • Both summaries are automatically generated using advanced AI technology
  • transcriptionSummary: Clinical format for healthcare providers with medical terminology
  • patientSummary: Simplified format for patients, written in plain language
  • Summaries include information from doctor's notes and prescriptions when available

MediLoan Integration

Loan Initiation

The content here describes how to initiate a loan via the SDK endpoint. This is the first step in the MediLoan process where the Organization (Hospital/HMO) initiates a request.

POST/sdk/loans/initiate

Request Parameters

FieldTypeRequiredDescription
clientReferencestringYesUnique identifier for this initiation in your system.
invoiceReferencestringNoReference to the invoice being funded. Can be duplicated.
firstNamestringYesFirst name of the patient/applicant.
phoneNumberstringYesPhone number of the patient. Formats: 080..., 234..., +234...
treatmentstringYesDescription of the treatment or service.
treatmentAmountnumberYesAmount in minor currency unit (e.g., kobo for NGN).

Code Example

curl -X POST https://api.myitura.com/api/v1/sdk/loans/initiate \
  -H "x-api-public-key: pk_live_abc123def456" \
  -H "x-api-secret-key: sk_live_xyz789uvw012" \
  -H "Content-Type: application/json" \
  -d '{
    "clientReference": "HOSP-001",
    "invoiceReference": "INV-2025-001",
    "firstName": "John",
    "phoneNumber": "08012345678",
    "treatment": "Surgery",
    "treatmentAmount": 500000
  }'

Response

Response
{
  "status": 201,
  "message": "Loan initiation successful",
  "data": {
    "sdkReference": "AB12CD34EF",
    "clientReference": "HOSP-001",
    "applicationUrl": "https://loans.myitura.com/loans/apply?sdkref=AB12CD34EF",
    "status": "initiated",
    "expiresAt": "2025-11-02T10:00:00.000Z"
  }
}

Webhook Integration

The MyItura MediLoan Webhook API allows your system to receive real-time notifications about loan life-cycle events initiated through your platform.

Security & Verification

To ensure that webhook requests originate from MyItura and have not been tampered with, every request includes a cryptographic signature in the header.

HeaderDescription
X-MyItura-EventThe type of event (e.g., loan.initiated).
X-MyItura-SignatureThe HMAC-SHA256 signature of the request body.

Signature Verification Example

// Signature verification is a server-side process

Event Types

  • loan.initiated: Triggered when a loan application is started.
  • loan.application.submitted: Triggered when KYC is completed.
  • loan.approved: Triggered when an admin approves the loan.
  • loan.rejected: Triggered when the loan is rejected.
  • loan.offer.accepted: Triggered when the user signs the offer.
  • loan.disbursed: Triggered when funds are transferred.

Payload Example (loan.initiated)

Response
{
  "event": "loan.initiated",
  "timestamp": "2024-01-08T14:30:00Z",
  "data": {
    "sdkReference": "SDK-A1B2C3D4",
    "clientReference": "YOUR_SYSTEM_REF_123",
    "invoiceReference": "INV-001",
    "status": "pending",
    "amount": 5000000,
    "currency": "NGN",
    "applicant": {
      "firstName": "John",
      "lastName": "Doe",
      "gender": "male"
    },
    "applicationUrl": "https://apply.myitura.com/loan?ref=SDK-A1B2C3D4"
  }
}

Lending Partner SDK

The Lending Partner SDK is for credit providers who underwrite on their own side and lend to MyItura patients. You push a credit decision, we turn it into a loan with a signed offer letter, your borrower accepts, and the funds land as a ring-fenced credit balance they can put toward healthcare on MyItura. Your book stays yours: the capital is your pool, the borrowers are your borrowers, and every event is mirrored back to you.

It uses the same SDK key pair as the MediLoan endpoints above, so if you are already integrated there, you are already authenticated here. Where MediLoan initiation hands a patient to MyItura's own lending, this is the reverse: you are the lender, and MyItura provides the rails.

Every amount is an integer in kobo. ₦50,000 is 5000000. The one exception is the payload you push us: Cortex decisions carry naira, and we convert them at the boundary. Never do money arithmetic in floats.

The lifecycle

A pre-approval moves through: received → offer_issued → accepted | declined | expired → disbursed. A decision you push as a decline is recorded as declined immediately and never becomes a loan. We keep it so your funnel and score history are complete.

  1. Push a decision: POST /v1/preapprovals with your Cortex payloads verbatim. Idempotent on assessment_id.
  2. Issue the offer: POST /v1/preapprovals/{id}/issue-offer (or automatically, if auto-issue is enabled for you). We render a tenant-branded offer letter and emit loan.offer.issued with a signed link to the PDF.
  3. Your borrower accepts, through our hosted checkout, or relayed by you via POST /v1/loans/{id}/accept-terms. Acceptance is always required before any money moves.
  4. Disburse: POST /v1/loans/{id}/disburse draws from your prefunded pool into the borrower's credit balance.
  5. Collect and record: debit on your own rail, then record it with method: "external".
We still apply our own controls. Your decision is a request to lend, not an instruction we execute blindly: MyItura runs its own AML/sanctions screen, enforces the rate and exposure caps agreed for your tenant, and requires borrower acceptance. A screen hit leaves the loan under review and issues no offer.

Approving directly

If you originate loans yourself rather than pushing pre-approvals, POST /v1/loans/{id}/approve and POST /v1/loans/{id}/reject are yours, both taking an optional notes or reason. Approving prices the loan, freezes its pricing snapshot, builds the schedule and issues the offer.

On a delegated-tier (Tier A) product you attest that you verified the borrower. Send that with the approval rather than as a separate call:

Response
POST /v1/loans/{id}/approve

{
  "notes": "Approved on our own underwriting",
  "attestation": {
    "schemaVersion": 1,
    "attestedChecks": {
      "identityVerified": true,
      "addressVerified": true,
      "sanctionsScreened": true
    }
  }
}

We record the attestation, run our AML screen, and only then approve. The response tells you which happened:

Response
{
  "data": {
    "approved": true,
    "loan": { "id": "...", "status": "awaiting_acceptance" },
    "attestation": { "amlScreenStatus": "clear" }
  }
}

// Screen hit: recorded, but NOT approved.
{
  "data": {
    "approved": false,
    "attestation": { "amlScreenStatus": "hit" }
  }
}
A screen hit is an outcome, not an error. The loan goes to under_review for a human at MyItura and issues no offer, so approved: false comes back with 200: there is nothing to retry. The same verdict is pushed as loan.kyc.attested, which is what to listen for if you approve asynchronously. Attesting separately with POST /v1/loans/{id}/attestation still works.

Authentication

Same credentials as the rest of the MyItura API: send your key pair as X-API-Public-Key and X-API-Secret-Key, or as a single Authorization: ApiKey <public>:<secret> header. Your organisation is always derived from the credential, never from anything in the path, query or body, so you can only ever read and write your own data.

Access to the pre-approval endpoints additionally requires the Pre-Approved Lending permission on your organisation, granted by MyItura when your program goes live. Without it these endpoints return 403 PREAPPROVED_LENDING_PERMISSION_DENIED.

Test-mode keys drive the sandbox and live keys drive production. The two stacks are separate deployments on separate stores, so a test key can never touch live data.
Some things are not on the API, on purpose. Defining a loan product and funding your capital pool are done by your staff on the MyItura dashboard, signed in. Funding moves real money out of your organisation wallet, so it is never something an API key can trigger. This page is the contract for what the keys can do.

Providers Directory

Lists the active MyItura healthcare providers (laboratories and pharmacies), so you can reference one as providerId when you push a decision. It serves exactly the public patient-facing directory: no permissions, contact details or configuration.

GET /v1/providers

curl -X GET "https://api.myitura.com/v1/providers?receiveTest=true&location=Lagos&limit=20" \
  -H "X-API-Public-Key: pk_live_abc123def456" \
  -H "X-API-Secret-Key: sk_live_xyz789uvw012"
Response
{
  "status": 200,
  "message": "Providers retrieved",
  "data": {
    "providers": [
      {
        "id": "9f1c2b64-0f1a-4c3e-9b8a-2d5e7f0a1b2c",
        "name": "Lagoon Diagnostics",
        "slug": "lagoon-diagnostics",
        "code": "LAGDIAG",
        "isActive": true,
        "receivesTests": true,
        "receivesDrugs": false,
        "providerType": "laboratory",
        "city": "Ikeja",
        "state": "Lagos",
        "organisationLogoUrl": "https://storage.googleapis.com/...signed..."
      }
    ],
    "pagination": { "page": 1, "limit": 20, "total": 42 }
  }
}

Filters: receiveTest=true narrows to laboratories, receiveDrug=true to pharmacies (only true is honoured: they narrow the directory, never widen it), name and location are partial matches, plus page / limit (max 100).

Loan Products

A product is the template a loan is priced and governed by: interest, fees, limits, and where the money settles. Your products are agreed with MyItura and set up by your staff on the dashboard. Over the API they are read-only, so you can look up the productId to reference on a push.

GET /v1/products

Lists your active products. GET /v1/products/{id} fetches one.

Response
{
  "data": {
    "products": [
      {
        "id": "3c1a9e77-5a2b-4f0d-9c3e-1b2a4d6f8e0c",
        "name": "Health Cash 90",
        "code": "HEALTH90",
        "currency": "NGN",
        "interestModel": "flat",
        "interestRateBps": 3600,
        "processingFeeBps": 100,
        "minAmountKobo": 100000,
        "maxAmountKobo": 100000000,
        "minTenorDays": 30,
        "maxTenorDays": 365,
        "isActive": true
      }
    ]
  }
}

Rates and fees are basis points, so 3600 is 36% and 100 is 1%. A pushed decision carrying its own rate is still checked against the cap agreed for your tenant, and refused at approval time if it exceeds it rather than being silently clamped.

Most partners never need this endpoint. If a default product is configured for your tenant, a pre-approval uses it automatically. Pass productId to POST /v1/preapprovals/{id}/issue-offer only when you deliberately want to override that choice.

Pre-Approvals

Push each Cortex decision as it is made. Send the decision and enriched payloads verbatim. We read what we need, keep the rest for audit, and scrub raw BVNs before anything is stored.

POST /v1/preapprovals

curl -X POST https://api.myitura.com/v1/preapprovals \
  -H "X-API-Public-Key: pk_live_abc123def456" \
  -H "X-API-Secret-Key: sk_live_xyz789uvw012" \
  -H "Idempotency-Key: push-6a3a8ae827a13ddd4c69b782" \
  -H "Content-Type: application/json" \
  -d '{
    "clientReference": "CRDCHK-2026-0001",
    "providerId": "9f1c2b64-0f1a-4c3e-9b8a-2d5e7f0a1b2c",
    "borrower": {
      "firstName": "Ada",
      "lastName": "Okoro",
      "email": "ada.okoro@example.com",
      "phone": "+2348012345678"
    },
    "decision": {
      "metadata": {
        "assessment_id": "6a3a8ae827a13ddd4c69b782",
        "bvn": "22200000001"
      },
      "decision": {
        "recommendation": "approve",
        "approved_amount": 150000.50,
        "confidence": "high",
        "conditions": {
          "interest_rate": 4.5,
          "tenor_days": 90,
          "repayment_frequency": "monthly"
        }
      },
      "trust_score": { "score": 720, "breakdown": { "risk_band": "Low Risk" } }
    },
    "enriched": { "enriched_data": { "income_band": "HIGH" } }
  }'
Response
{
  "status": 202,
  "message": "Pre-approval received",
  "data": {
    "preapprovalId": "b4d1f7a2-1c3e-4f5a-8b9c-0d1e2f3a4b5c",
    "assessmentId": "6a3a8ae827a13ddd4c69b782",
    "status": "received",
    "recommendation": "approve",
    "approvedAmountKobo": 15000050,
    "borrowerId": "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
    "expiresAt": "2026-08-15T00:00:00Z"
  }
}

What we read from your decision

Your fieldRequiredBecomes
metadata.assessment_idYesThe idempotency key. Replaying it returns the original record.
metadata.bvnYesMatched against verified MyItura KYC to link an existing user; stored only as a keyed hash. Required on every decision: it is the only identifier that ties a borrower to bureau data and to a verified MyItura user. A metadata.nin is accepted and kept when you have one, but is never required, since Cortex decisions are BVN-keyed.
decision.recommendationYesdecline is recorded and stops there; anything else is eligible for an offer.
decision.approved_amountFor offersThe loan principal. Naira → approvedAmountKobo.
decision.conditions.interest_rateFor offersThe rate charged (percent → basis points), subject to your agreed cap.
decision.conditions.tenor_daysFor offersThe loan tenor. Without it we cannot price the loan and no offer can be issued.
trust_score.score / breakdown.risk_bandNoRecorded for your funnel and audit history.
Reading them back: GET /v1/preapprovals?status=received&limit=50 lists yours, and GET /v1/preapprovals/{id} fetches one. The raw payloads are never echoed back.

Offers & Acceptance

Issuing an offer creates the loan on your pushed terms and renders a branded offer letter PDF. We emit loan.offer.issued carrying a short-lived signed link. Relay the document to your borrower for acceptance.

POST /v1/preapprovals/{id}/issue-offer

curl -X POST https://api.myitura.com/v1/preapprovals/{preapprovalId}/issue-offer \
  -H "X-API-Public-Key: pk_live_abc123def456" \
  -H "X-API-Secret-Key: sk_live_xyz789uvw012" \
  -H "Idempotency-Key: offer-6a3a8ae827a13ddd4c69b782" \
  -H "Content-Type: application/json" \
  -d '{}'

Idempotent: once a pre-approval has a loan, the same loan is returned. Pass productId to override the loan product configured for you. Rejected with 400 if the decision was a decline, has expired, carries no tenor, or if our AML screen flagged the borrower.

Offer letter links expire. Fetch the PDF as soon as you receive the webhook; do not store the URL. Need it again later? Call GET /v1/loans/{id}/offer-letter for a fresh link. Once accepted, the stored letter is re-rendered with the signature block.

Accept or decline

Your borrower can accept through our hosted checkout, or you can relay their decision: POST /v1/loans/{id}/accept-terms or POST /v1/loans/{id}/decline-terms (optionally with reason). Both are terminal for the offer, and a declined loan cannot be revived, so push a fresh decision instead. Only after acceptance will POST /v1/loans/{id}/disburse release funds.

Cancelling a loan

POST /v1/loans/{id}/cancel, optionally with {"reason": "..."}, calls a loan off. It is idempotent, and the reason is recorded on the loan and carried on the loan.cancelled event.

This is broader than declining. Declining answers an offer that is currently on the table; cancelling works from the moment a loan exists right up to disbursement, including while it is still waiting on KYC or approval, when there is no offer to decline. Your borrower can also cancel themselves from the hosted checkout, so the event carries cancelledBy (tenant, borrower or admin): both land on cancelled, and you will want to tell them apart when you reconcile.

Disbursement is the point of no return. Once funds are released, cancelling is refused with 400: the money is with your borrower, so the loan has to run to completion, default or recovery. If you want an escape hatch after disbursement, that is a repayment, not a cancellation.

Recording Repayments

You run collections on your own direct-debit rail. When a debit succeeds, record it against the loan. That is what returns principal to your pool and settles the cost of credit.

POST /v1/loans/{id}/repayments

curl -X POST https://api.myitura.com/v1/loans/{loanId}/repayments \
  -H "X-API-Public-Key: pk_live_abc123def456" \
  -H "X-API-Secret-Key: sk_live_xyz789uvw012" \
  -H "Idempotency-Key: repay-CRDCHK-DD-88421" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "external",
    "amountKobo": 5000000,
    "externalReference": "CRDCHK-DD-88421"
  }'
Response
{
  "status": 201,
  "message": "Repayment recorded",
  "data": {
    "id": "c5e2a8b3-2d4f-5a6b-9c0d-1e2f3a4b5c6d",
    "loanId": "d6f3b9c4-3e5a-6b7c-0d1e-2f3a4b5c6d7e",
    "method": "external",
    "status": "completed",
    "amountKobo": 5000000,
    "feePortionKobo": 500000,
    "interestPortionKobo": 1500000,
    "principalPortionKobo": 3000000,
    "externalReference": "CRDCHK-DD-88421"
  }
}

Methods

MethodUse it when
externalYour default. You collected on your own rail and are recording the result. Idempotent on externalReference, so it is safe to retry.
cash_walletDebit the borrower's own MyItura cash wallet (what their bank transfer pays into). Requires a claimed MyItura account with sufficient balance.
walletSweep the borrower's undrawn credit back to your pool, not a collection from the borrower.
mandateOnly for MyItura-operated Paystack mandates. Not available for partner-run direct debit.

Repayments settle in strict order: fees, then interest, then principal, and any amount beyond what is outstanding is not allocated. Each one advances the schedule and emits loan.repayment.completed; the final one completes the loan and emits loan.completed.

Mandates

POST /v1/loans/{id}/mandates creates a direct-debit mandate. email is required; bankCode, accountNumber and callbackUrl are optional.

POST /v1/loans/{id}/mandates

Response
{
  "data": {
    "id": "c7d8e9f0-1a2b-3c4d-5e6f-7a8b9c0d1e2f",
    "loanId": "8b7c6d5e-4f3a-2b1c-0d9e-8f7a6b5c4d3e",
    "provider": "paystack",
    "status": "pending",
    "reference": "MND-8f7a6b5c",
    "authorizationUrl": "https://checkout.paystack.com/qd4hs9x2mn"
  }
}

Send your borrower to authorizationUrl. That page is where they authorise the debit, and it is the only thing that can activate a mandate. Once they are done, call POST /v1/mandates/{id}/activate (no body) and we verify the authorisation with the provider and flip the mandate to active. Poll GET /v1/mandates/{id} if you would rather check first.

We will not take an authorisation code from you. activate accepts no body. An authorisation code is proof that one specific person allowed one specific account to be debited, so the only one we trust is the one we read back from the provider ourselves. Send the borrower to the URL; we do the rest.
Partner-operated mandates are different. {"provider": "creditcheck"} records that your own direct debit is in force. Nothing is initialised with Paystack, there is no authorizationUrl and nothing to activate: it is a status marker only, we hold no authorisation and never debit it. Keep recording your collections with method: "external".
Bank-transfer repayment limits. If your borrowers repay by transferring into their MyItura virtual account, Tier-1 accounts cap at ₦10,000 per day and ₦500,000 per month. Borrowers on larger loans need a tier upgrade, so talk to us before launching that channel.

Webhooks

Register endpoints with POST /v1/webhook-endpoints. The signing secret (whsec_…) is returned once and never again. Every delivery is {"event", "timestamp", "data"} signed with HMAC-SHA256.

An endpoint receives every event unless you send an events list, which is matched on exact names: loan.* is not a pattern, and an endpoint filtered to loan.approved will not receive loan.offer.issued. List each event you want, or omit events entirely.

Verifying a delivery

# Webhooks are delivered to the endpoint you register. Verify every delivery
# before acting on it. See the language tabs for a real implementation.
#
# Headers on each delivery:
#   X-MyItura-Signature-V2: hex HMAC-SHA256 of "<timestamp>.<raw body>"
#   X-MyItura-Timestamp:    unix seconds
#   X-MyItura-Signature:    hex HMAC-SHA256 of the raw body (legacy)

Events

EventMeaning
preapproval.receivedWe stored your pushed decision. Use it to prove your receiver works end to end.
preapproval.expiredAn unconsumed decision lapsed before an offer was issued.
loan.approvedThe loan was created and priced on your terms. Carries offerLetterUrl (short-lived) and assessmentId, so subscribing to this event alone is enough to act on an approval.
loan.offer.issuedThe offer letter is ready. Same payload as loan.approved, fired immediately after it. Relay it to your borrower.
loan.offer.acceptedTerms accepted, so the loan can now be disbursed.
loan.offer.declinedYour borrower turned the offer down. Terminal.
loan.cancelledCalled off before disbursement. Carries cancelledBy (tenant, borrower or admin) and the reason. Terminal.
loan.kyc.attestedOur AML screen ran on your attestation. Carries amlScreenStatus: on a hit the loan goes to under_review and no offer is issued, so this is how you learn a loan has stalled.
loan.disbursedFunds released from your pool into the borrower's credit balance.
loan.repayment.completedA repayment settled; carries the new outstandingKobo.
loan.completedFully repaid.
loan.defaultedRecovery escalated the loan to default.

Every payload is tagged with its mode (live/test) and is only delivered to endpoints registered for that mode, so test traffic can never reach a live receiver. Failures retry with exponential backoff; repeated failures disable an endpoint. Inspect and requeue with GET /v1/events and POST /v1/events/{id}/redeliver.

Reconciliation

One call gives you the whole program: what you pushed, what converted, and what the money is doing. It counts only loans that came from your pushed decisions.

GET /v1/preapprovals/funnel

curl -X GET https://api.myitura.com/v1/preapprovals/funnel \
  -H "X-API-Public-Key: pk_live_abc123def456" \
  -H "X-API-Secret-Key: sk_live_xyz789uvw012"
Response
{
  "status": 200,
  "message": "Pre-approval funnel retrieved",
  "data": {
    "pushed": 1240,
    "declinedByPartner": 310,
    "awaitingOffer": 12,
    "offerIssued": 48,
    "acceptedByBorrower": 60,
    "declinedByBorrower": 22,
    "expired": 15,
    "disbursed": 773,
    "approvedAmountKobo": 92500000000,
    "disbursedAmountKobo": 71200000000,
    "repaidAmountKobo": 41850000000,
    "outstandingKobo": 29350000000
  }
}

Settlements

GET /v1/settlements lists the statements for what MyItura owes you: gross collected, less the platform fee, equals net payable. GET /v1/settlements/{id} fetches one, and GET /v1/settlements/{id}/csv returns the same statement as CSV for your finance team to reconcile line by line.

Your loan book

GET /v1/loans is your book, paginated and filterable. Only ever your own loans: the organisation comes from your credential, so there is no tenant id to pass and no way to ask for anyone else's.

GET /v1/loans

Response
GET /v1/loans?status=active&page=1&limit=50

{
  "data": {
    "loans": [
      {
        "id": "8b7c6d5e-4f3a-2b1c-0d9e-8f7a6b5c4d3e",
        "clientReference": "CRDCHK-2026-0001",
        "status": "active",
        "principalKobo": 15000050,
        "outstandingKobo": 11000050,
        "tenorDays": 90,
        "disbursedAt": "2026-07-01T10:04:00Z"
      }
    ],
    "pagination": { "page": 1, "limit": 50, "total": 773 }
  }
}
QueryMeaning
statusAny loan status, for example active or cancelled. An unknown value is rejected with 422 rather than silently returning nothing.
borrowerIdEvery loan for one borrower.
clientReferenceFind a loan by your own reference.
page, limit1-based. limit defaults to 20, capped at 100. pagination.total is the full count matching your filter, so you can page to the end instead of probing for an empty page.

GET /v1/loans/{id} is the full loan. GET /v1/loans/{id}/schedule and GET /v1/loans/{id}/repayments give the instalments and what has been collected against them. GET /v1/usage?days=30 is your own API usage rollup.

Your capital position is on the dashboard, alongside the funding controls, rather than on the API. If a disbursement fails for insufficient capital you will get a clear error on POST /v1/loans/{id}/disburse, and topping up is a signed-in action for your staff.

Idempotency & Errors

Send an Idempotency-Key header on every POST, especially money movements. The first request executes and its response is cached per key; retries replay it with Idempotent-Replayed: true. Reusing a key on a different method or path returns 409, as does retrying while the original is still in flight. Transient 5xx outcomes are not cached, so they are always safe to retry.

Pushes and collections carry their own natural keys too: assessment_id for a decision, externalReference for a repayment, so a replay is a no-op even without the header.

StatusWhat it means
202Decision received and stored.
200Idempotent replay: we already had this assessment.
401Missing or invalid API credentials.
403Your organisation lacks Loan Tenant or Pre-Approved Lending, or the push is not enabled for your tenant.
404Not found within your organisation: another tenant's records are never visible.
409Idempotency key conflict.
422The payload is malformed or unusable (e.g. no assessment_id, or an amount we cannot convert).
429Rate limited, so back off and retry.