My EMI Pay
    • My-EMI Lending API
    • System
      • Health check
        GET
    • Products
      • List published loan products
        GET
    • Auth
      • Request OTP
        POST
      • Verify OTP
        POST
      • Refresh tokens
        POST
      • Select a family account after OTP
        POST
      • Add a family member on this phone
        POST
      • Logout
        POST
      • Send email verification link
        POST
      • Email verification status
        GET
      • Confirm email verification (Bearer)
        GET
      • Confirm email verification token (Bearer)
        POST
      • iOS Universal Links association
        GET
      • Android App Links association
        GET
    • Profile
      • Get customer profile
        GET
      • Update customer profile
        POST
    • Applications
      • Get current application screen
        GET
      • Begin verification
        POST
      • Verify PAN
        POST
      • Acknowledge credit score
        POST
      • Accept offer
        POST
      • Create loan
        POST
      • Request payout
        POST
    • Application journey
      • Application journey
      • Start PAN KYC via DigiLocker
      • PAN KYC status
      • Start Aadhaar KYC via DigiLocker
      • Aadhaar KYC status
      • Edit Aadhaar communication address
      • Verify bank account (KYC)
      • Start eSign
      • eSign status
      • Bureau report
      • Loan eligibility
    • Applications (staff)
      • Pull bureau (staff/system)
      • Evaluate and sanction (staff/system)
      • Cancel application (staff/system)
    • Applications (deprecated)
      • Get application screen by id (deprecated)
      • Begin verification (by id) (deprecated)
      • Verify PAN (by id) (deprecated)
      • Acknowledge credit score (by id) (deprecated)
      • Accept offer (by id) (deprecated)
      • Create loan (by id) (deprecated)
      • Request payout (by id) (deprecated)
    • Webhooks
      • Resolve or fail bureau claim
      • Resolve or fail KYC claim
      • Resolve or fail eSign claim
      • Resolve or fail income claim
    • Ops
      • Dispatch due outbox handlers
      • Poll workflow timers
      • Redrive dead-letter handler

    My-EMI Lending API

    Customer app, staff underwriting, vendor callbacks, and ops.
    After a successful login, tokens and ids are saved for the next request. Follow the flow below — skipping a step is how you get 409 / 422 or a screen that still shows the previous action.
    Start a run: GET /products → OTP request → OTP verify. Then stay on Bearer.

    Ground rules#

    Money is integer paise. ₹15,000 = 1500000. Never send rupees as a decimal.
    Ids look like cust_1735…, app_1735… — not UUID-shaped. Screen reference PME-XXXXXXXX.
    Failures look like { "code": "snake_case", "message": "plain English" }.
    Revision: application writes need a fresh revision from the last screen. Stale → 409 revision_conflict. Refresh GET /applications before a write.
    There is no create-application API. OTP verify starts the case when isNewUser is true.
    Customer URLs have no application id. The session picks the open application. Staff URLs still use /applications/{id}.

    Who calls what#

    WhoHowWhat to use
    Anyoneno loginHealth, Products, OTP, token refresh
    CustomerBearer accessTokenProfile, Applications, Journey, email verify
    Staffstaff actor (local hatch)Evaluate, verify-bureau, cancel
    OpsX-Internal-Secretdispatch-due, poll-timers, redrive
    Vendorsigned webhook/webhooks/*

    Customer flow — what to call, when, and why#

    1. Is the API up?#

    Call GET /health.
    Why: wrong host looks like “login is broken”. This only proves the process is listening.

    2. Pick a loan product#

    Call GET /products before OTP.
    Why: every OTP is tied to a published product_id. The session stays on that product. Unknown id → invalid_product.
    Then: copy products[n].id into product_id (first product is saved automatically).

    3. Login (this also starts the application)#

    Call POST /auth/otp/request with phone + product_id.
    Why: a one-time code is stored. It is not in the JSON. Mock code is 123456 unless QA says otherwise.
    Then POST /auth/otp/verify with the same phone, same product_id, and the code.
    Why: this returns phoneSessionToken always. One account also gets accessToken / refreshToken / customerId and application (auto-started when isNewUser is true). Two family accounts → pick with POST /auth/accounts/select. From here every customer URL needs Bearer.
    Token expired → POST /auth/token/refresh. Sign out → POST /auth/logout.

    4. Email (optional to log in; required if you collect email)#

    Call POST /auth/email/verify/start → customer taps the mail → POST /auth/email/verify/confirm with token + Bearer.
    Why: email cannot be saved on the profile POST. Confirm sets emailVerified: true. Poll GET /auth/email/verify/status while waiting.

    5. Bind and save the profile#

    Call GET /application/profile as soon as you have a token.
    Why: pre-populate phone / email / KYC name. null means unknown, not “field missing”.
    When the current step is update_profile, call POST /application/profile (fullName, dob as dd-mm-yyyy, address string, employment type, employer name, monthlyIncomePaise).
    Why: PAN/Aadhaar matching and later bank-name check need these on file.

    6. Read the screen, then begin verification#

    Call GET /applications (status, youCanNow, revision) and/or GET /application/journey (current.step, upcoming).
    Why: do not guess the next URL. Use youCanNow[].submitTo or current.path.
    If status is started, call POST /applications/begin-verification.
    Why: started → verifying. KYC is not allowed before this.

    7. PAN → bureau → customer sees the score#

    Call POST /application/kyc/pan (or POST /applications/verify-pan).
    Why: DigiLocker PAN. Open redirectUrl, finish consent, then poll GET /application/kyc/pan/status until resolved + verified. If PAN is not in the locker, status stays pending and returns redirectUrl again.
    Then GET /application/bureau.
    Why: the credit-score screen needs score. Acknowledge too early → credit_score_not_ready.
    Then POST /applications/acknowledge-credit-score.
    Why: the borrower saw the score. This is not an offer. Staff still evaluate.

    8. Aadhaar, then wait — do not jump to bank#

    Call POST /application/kyc/aadhaar/start → open redirectUrl → poll GET /application/kyc/aadhaar/status.
    Why: Aadhaar is matched to PAN name/DOB. Optional POST /application/kyc/aadhaar/address if they want a different communication address.
    When Aadhaar is verified, onboarding is finished and current is under_verification.
    Why you wait: underwriting. Bank KYC and eSign are after the offer.

    9. Staff sanction (not the customer app)#

    Call (staff) POST /applications/{id}/evaluate with bureauClaimId, amount in paise, and the rule-set fields.
    Why: this is the only step that creates offer_ready (or rejects). A customer token here is 403.
    Then GET /application/eligibility. If eligible: true, show amountPaise and call POST /applications/accept-offer.

    10. After accept: bank, eSign, loan, payout#

    Call POST /application/kyc/bank then POST /application/esign/start (poll GET /application/esign/status).
    Then POST /applications/create-loan → POST /applications/request-payout.
    Why: one loan per application. 202 on payout means “request accepted”, not “money sent”.
    Then ops POST /ops/dispatch-due so payout actually runs.

    How to pick the next API#

    Prefer GET /application/journey → call current.method + current.path.
    Or GET /applications → youCanNow.
    NowCall thisWhy
    no tokenproducts → OTP request → verifyLogin + auto-start
    startedPOST /applications/begin-verificationUnlock KYC
    update_profilePOST /application/profileName/DOB/income before PAN
    kyc_panPAN start + status pollDigiLocker PAN
    bureauGET /application/bureauScore for the next screen
    acknowledge_credit_scorePOST /applications/acknowledge-credit-scoreCustomer saw the score
    kyc_aadhaarAadhaar start + status pollIdentity vs PAN
    under_verificationwait; staff evaluateUnderwriting
    eligibility / offer_readyeligibility GET, then accept-offerShow amount, take consent
    acceptedbank → esign → create-loanAfter the offer
    loan existsrequest-payout, then ops dispatchDisbursal
    Refresh GET /applications before a write that needs revision.

    Other flows#

    Cancel an offer (staff): POST /applications/{id}/cancel from sanctioned. Terminal.
    Vendor callbacks: POST /webhooks/bureau|kyc|esign|income with a signature. claimId is in the body.
    Stuck payout: POST /ops/redrive/{handlerRowId} then dispatch-due again.
    Deprecated folder: old by-id customer aliases. Do not use them unless a ticket says so.

    Values saved after a good response#

    ValueComes fromUsed for
    product_idGET /products, OTP verifyOTP body
    accessToken / refreshToken / customerIdOTP verify, refreshlogin
    applicationId / revisionscreens, journey, verifystaff path, writes
    redirectUrlKYC start/statusopen DigiLocker
    claimIdstaff verify-bureau, webhooksevaluate
    loanId / payoutRequestIdcreate-loan, request-payoutpayout
    journeyCurrent / journeyUpcomingGET /application/journeynext step
    lastErrorCodeany error JSONdebug
    Modified at 2026-09-09 21:04:00
    Next
    System
    Built with