Happy House - Ecommerce Docs

EMI Calculator API Reference

Complete API contracts for the EMI Calculator module, including routes, auth, DTOs, responses, errors, examples, and integration notes.

EMI Calculator - API Reference

Audience: Frontend engineers (storefront + admin panel), backend engineers, QA, and API consumers. Scope: The 25 routes — 2 public customer routes under /api/mobile/emi/..., 23 admin routes under /api/admin/emi/....

1. Documentation Evidence

AreaFiles InspectedWhat Was Verified
Controllersapps/api/src/modules/emi/customer/emi-customer.controller.ts, admin/{settings,tenure,eligibility,bank,bank-rate,bank-tenure}/*.controller.tsRoutes, methods, guards, permissions, rate limits
DTOscustomer/dto/*.ts, admin/*/dto/*.tsRequest/query/response validation, examples
Servicescustomer/emi-customer.service.ts, shared/*.service.ts, admin/*/*.service.tsBehavior, error codes
Schemapackages/db/src/schema/emi/*.tsTables, enums, constraints
Mathapps/api/src/utils/emi/emi-math.util.tsExact arithmetic, caps
Existing docsconsumer-handoff.md, Fumadocs formatsFrozen contract and format baseline

2. Module Summary

FieldValue
Module nameemi
Module slugemi
Primary actorsguest (customer), admin (merchandiser), admin (rate-entry)
API surfacesmobile (2 public), admin (23)
Base route prefixes/api/mobile/emi/products/{productId}/..., /api/admin/emi/...
Auth model@Public() (customer); JwtAuthGuard + RoleGuard + Emi_*/EmiBanks_* (admin)
PersistencePostgreSQL (6 tables); Redis cache (emi:v1: keys)
Runtime source of truthSettings row + rate history + selection tables; quotes from computeEmi
Sibling docsBackend, Features and flows

3. Concepts and Terminology

TermMeaningSource FileUsed By
annualRateBpsBasis points — hundredths of a percent. 1200 = 12.00%emi-math.util.tsCustomer + admin responses
basePriceThe price the quote is based on (selling price or MRP per settings)customer serviceOptions + calculate
isApplicableBank's loan limits contain the product price (inclusive)customer serviceOptions
isEstimateAlways true — no loan exists; equal installments cannot exactly retire an amortising loancustomer serviceCalculate
tenureMonthsRepayment period, integer 1…120 — the exponent of the calculationmath utilBoth customer routes
effectiveFromWhen a rate takes force — full ISO instantrate schemaRate admin
OfferableActive + not deleted + rate in force + ≥1 tenurecatalog serviceCustomer routes

4. API Surface Map

SurfaceMethodPathActorAuth/GuardPermissionControllerPurpose
MobileGET/api/mobile/emi/products/{productId}/optionsGuest@Public()EmiCustomerBank comparison with computed quotes
MobileGET/api/mobile/emi/products/{productId}/calculateGuest@Public()EmiCustomerOne exact breakdown
AdminGET/api/admin/emi/settingsAdminJWT+RoleEmi_READSettingsRead the switch/source/mode
AdminPUT/api/admin/emi/settingsAdminJWT+RoleEmi_UPDATESettingsFlip the switch, set source/mode
AdminGET/api/admin/emi/tenuresAdminJWT+RoleEmi_READTenureList the vocabulary
AdminPUT/api/admin/emi/tenuresAdminJWT+RoleEmi_UPDATETenureReplace the vocabulary
AdminGET/api/admin/emi/eligibility/categoriesAdminJWT+RoleEmi_READEligibilityList selected categories
AdminPOST/api/admin/emi/eligibility/categoriesAdminJWT+RoleEmi_CREATEEligibilitySelect a category
AdminDELETE/api/admin/emi/eligibility/categories/{categoryId}AdminJWT+RoleEmi_DELETEEligibilityDeselect
AdminGET/api/admin/emi/eligibility/productsAdminJWT+RoleEmi_READEligibilityList selected products
AdminPOST/api/admin/emi/eligibility/productsAdminJWT+RoleEmi_CREATEEligibilitySelect a product
AdminDELETE/api/admin/emi/eligibility/products/{productId}AdminJWT+RoleEmi_DELETEEligibilityDeselect
AdminGET/api/admin/emi/eligibility/effective-productsAdminJWT+RoleEmi_READEligibilityWhat customers would see
AdminGET/api/admin/emi/banksAdminJWT+RoleEmiBanks_READBankList (sorted, paginated)
AdminPOST/api/admin/emi/banksAdminJWT+RoleEmiBanks_CREATEBankCreate
AdminPATCH/api/admin/emi/banks/reorderAdminJWT+RoleEmiBanks_UPDATEBankReorder display
AdminGET/api/admin/emi/banks/{bankId}AdminJWT+RoleEmiBanks_READBankDetail
AdminPATCH/api/admin/emi/banks/{bankId}AdminJWT+RoleEmiBanks_UPDATEBankUpdate
AdminDELETE/api/admin/emi/banks/{bankId}AdminJWT+RoleEmiBanks_DELETEBankSoft delete
AdminPOST/api/admin/emi/banks/{bankId}/restoreAdminJWT+RoleEmiBanks_RESTOREBankRestore
AdminGET/api/admin/emi/banks/{bankId}/ratesAdminJWT+RoleEmiBanks_READRateRate history
AdminPOST/api/admin/emi/banks/{bankId}/ratesAdminJWT+RoleEmiBanks_CREATERateSchedule a rate
AdminDELETE/api/admin/emi/banks/{bankId}/rates/{rateId}AdminJWT+RoleEmiBanks_DELETERateCancel a future rate
AdminGET/api/admin/emi/banks/{bankId}/tenuresAdminJWT+RoleEmiBanks_READBankTenureOffered periods
AdminPUT/api/admin/emi/banks/{bankId}/tenuresAdminJWT+RoleEmiBanks_UPDATEBankTenureReplace offered periods

5. Auth, Identity, and Permissions

SurfaceGuard/DecoratorIdentity ShapePermissionGuest AllowedNotes
Customer@Public()NoneYesPUBLIC_READ 60/min; no guest identity
AdminJwtAuthGuard, RoleGuardreq.user.idEmi_* or EmiBanks_*NoADMIN_READ 30/min reads, ADMIN_WRITE 10/min mutations

Two permission modules. Emi_* = switch, price source, eligibility mode, tenure vocabulary, product/category eligibility. EmiBanks_* = banks, rates, tenures. Emi_UPDATE is the wider grant — it carries the store-wide kill switch.

6. DTO and Model Reference

6.1 EmiProductParamsDto — customer routes

FieldTypeRequiredValidation
productIduuidYesuuid v7 — a v4 is rejected VALIDATION_FAILED

6.2 EmiVariantQueryDto/options, and CalculateEmiQueryDto/calculate

Both routes bind their query with whitelist: true, forbidNonWhitelisted: true (the global ValidationPipe), so any unexpected query key — a campaign tag like utm_source, a cache-buster like ?_=169… — is a 400 VALIDATION_FAILED on what used to be a lenient public route. This is a genuine behavior change from before the variant work: /options took no query DTO at all previously.

FieldTypeRequiredValidationNotes
variantId (/options, via EmiVariantQueryDto)uuidNouuid v7product_variant.public_id. Omit for the product-page "from" estimate (cheapest live configuration); supply it once the customer has chosen. A variant that is not live, or belongs to a different product, is 404 EMI_VARIANT_NOT_FOUND — an identity failure, not an eligibility answer
bankId (/calculate)uuidYesuuid v7
variantId (/calculate, on CalculateEmiQueryDto)uuidNouuid v7Same meaning and same EMI_VARIANT_NOT_FOUND rule; pass the same variantId used to list the options or the plan quoted back is a different configuration's
tenureMonths (/calculate)numberYesinteger 1…12012.5 or 1000000 → VALIDATION_FAILED

6.3 UpdateEmiSettingsDtoPUT /settings

FieldTypeRequiredValidation
isEnabledbooleanYes
priceSourceenumYesselling_price / mrp
eligibilityModeenumYesentire_store / selected

6.4 Bank DTOs

CreateEmiBankDto: name (non-blank, ≤255), logoKey/logoAlt (alt requires key), minLoanAmount / maxLoanAmount (integers, min ≤ max, nullable), displayOrder (≥0). UpdateEmiBankDto: same fields, optional. ReorderEmiBanksDto: bankIds array.

6.5 Rate DTO

CreateEmiBankRateDto: annualRateBps (integer 0…10000), effectiveFrom (ISO instant, bounded < 2100).

6.6 Response DTOs

  • EmiOptionsDto: available, unavailableReason, productId, productName, variantId (uuid | null — the configuration these quotes are for; null means basePrice is the product's "from" figure), variantName (string | null — null both when no variant was requested AND when the requested variant is the product's sole configuration with no label of its own), basePrice (0 when unavailable), banks[] (see the handoff body in §8.1).
  • EmiCalculationDto: productId, productName, variantId (uuid | null — the configuration financed; null means the quote is the product's "from" estimate and must not be presented beside a chosen variant), variantName (string | null), bankId, bankName, basePrice, annualRateBps, tenureMonths, monthlyEmi, totalRepayment, totalInterest, isEstimate: true.
  • EmiBankAdminDto / EmiBankRateAdminDto / EmiTenureAdminDto / settings DTO: admin CRUD shapes, all money in paisa, rates in bps.

7. Enum Reference

EnumValueMeaningRuntime EffectSource
emi_price_sourceselling_price / mrpWhat the quote is based onExactly two — the products CHECK makes "higher/lower of" meaninglessenums.ts
emi_eligibility_modeentire_store / selectedHow the eligible set is decidedselected = union of both selection tablesenums.ts

8. Endpoint Reference

8.1 GET /api/mobile/emi/products/{productId}/options

Purpose

The storefront asks this before rendering any "Calculate EMI" affordance. It returns eligibility, the base price, and every offerable bank with its rate, loan limits, applicability and already computed quotes per offered period. It never returns 4xx for an ineligible productavailable: false distinguishes "no EMI" from "no such product". Only a genuinely missing product 404s, and so does a variantId that names no live configuration OF THIS PRODUCT — that is an identity failure, not an eligibility answer.

Pass variantId (optional, EmiVariantQueryDto) once the customer has chosen a configuration. Without it the quotes are the product's "from" estimate, computed from the cheapest live configuration, and variantId comes back null in the response so a client can tell which it received.

Auth and Permissions

  • Auth: @Public() · Rate limit: PUBLIC_READ 60/min

Response

{
  "success": true,
  "message": "EMI options fetched successfully",
  "data": {
    "available": true,
    "unavailableReason": null,
    "productId": "019fcc90-e010-7698-ae7a-faec1e7bed42",
    "productName": "Galaxy S24 Ultra",
    "variantId": "019fcc90-1234-7698-ae7a-faec1e7bed99",
    "variantName": "512GB",
    "basePrice": 13120091,
    "banks": [
      {
        "id": "0199b000-0000-7000-8000-00000000ba01",
        "name": "Nabil Bank",
        "logo": { "url": "/uploads/banks/nabil.png", "alt": "Nabil Bank" },
        "annualRateBps": 1200,
        "minLoanAmount": 500000,
        "maxLoanAmount": 50000000,
        "isApplicable": true,
        "unavailableReason": null,
        "quotes": [
          { "tenureMonths": 6,  "monthlyEmi": 2263850, "totalRepayment": 13583100, "totalInterest": 463009 },
          { "tenureMonths": 12, "monthlyEmi": 1165704, "totalRepayment": 13988448, "totalInterest": 868357 }
        ]
      }
    ]
  }
}

When unavailable: available: false, unavailableReason populated, basePrice: 0, banks: [].

quotes is already computed — render those numbers, do not recompute them. A bank with isApplicable: false has empty quotes and a populated unavailableReason (price outside the bank's loan limits).

Side Effects

None — reads only.

Error Cases

HTTPCodeCondition
404EMI_PRODUCT_NOT_FOUNDProduct missing, deleted, or not published/unlisted
404EMI_VARIANT_NOT_FOUNDvariantId supplied, and it is not live, or belongs to a different product
400VALIDATION_FAILEDBad variantId uuid, or any query key other than variantId (forbidNonWhitelisted)

8.2 GET /api/mobile/emi/products/{productId}/calculate

Purpose

One bank, one tenure, full exact breakdown. The client has already seen what is on offer, so a failure here means something changed underneath or the request was hand-made — every failure raises a specific code.

Pass the same variantId that was used to list the options, or the plan quoted back will be a different configuration's. The response echoes variantId and variantName so a client can detect both a reprice and a configuration mismatch.

Auth and Permissions

  • Auth: @Public() · Rate limit: PUBLIC_READ 60/min

Request

PartRequiredDetails
ParamsYesproductId (uuid v7)
QueryYesbankId (uuid v7), tenureMonths (int 1…120); variantId (uuid v7) optional — omitting it prices the product's "from" configuration. Any OTHER query key is a 400 (forbidNonWhitelisted)

Response

{
  "success": true,
  "message": "EMI calculated successfully",
  "data": {
    "productId": "019fcc90-…", "productName": "Galaxy S24 Ultra",
    "variantId": "019fcc90-1234-7698-ae7a-faec1e7bed99", "variantName": "512GB",
    "bankId": "0199b000-…",   "bankName": "Nabil Bank",
    "basePrice": 13120091,
    "annualRateBps": 1200,
    "tenureMonths": 12,
    "monthlyEmi": 1165704,
    "totalRepayment": 13988448,
    "totalInterest": 868357,
    "isEstimate": true
  }
}

basePrice is echoed so the client can detect the product was repriced between listing options and calculating.

Side Effects

None. The server computes it as an exact rational in BigInt with no floating point anywhere — do not recompute client-side; the server is right.

Error Cases

HTTPCodeCondition
403EMI_DISABLEDFeature off store-wide
403EMI_PRODUCT_NOT_ELIGIBLEProduct not on the scheme
404EMI_PRODUCT_NOT_FOUNDNo such live product
404EMI_VARIANT_NOT_FOUNDvariantId supplied, and it is not live, or belongs to a different product
409EMI_PRICE_NOT_AVAILABLEPrice resolves to zero
409EMI_NO_BANK_AVAILABLENo participating bank offerable
404EMI_BANK_NOT_FOUNDUnknown or no-longer-offered bank
400EMI_TENURE_NOT_SUPPORTEDThat bank does not offer the period
409EMI_AMOUNT_BELOW_BANK_MINIMUMPrice under the bank's floor
409EMI_AMOUNT_ABOVE_BANK_MAXIMUMPrice over the bank's ceiling
422EMI_CALCULATION_OUT_OF_RANGEShould never happen — report it
400VALIDATION_FAILEDBad uuid, non-integer or out-of-range tenure, or any query key other than bankId/variantId/tenureMonths (forbidNonWhitelisted)

8.3 GET /api/admin/emi/settings

Purpose

Read the store-wide switch, price source and eligibility mode.

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: Emi_READ · Rate limit: ADMIN_READ

8.4 PUT /api/admin/emi/settings

Purpose

Flip the store-wide switch, set the price source, set the eligibility mode. Takes effect within 30 seconds (config cache).

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: Emi_UPDATE · Rate limit: ADMIN_WRITE

Side Effects

Singleton upsert (CHECK (id = 1)); emi cache domain invalidated after commit.

Error Cases

HTTPCodeCondition
400validationMissing/invalid fields

8.5 GET /api/admin/emi/tenures

Purpose

List the tenure vocabulary (~9 seeded periods: 3, 6, 9, 12, 18, 24, 36, 48, 60).

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: Emi_READ · Rate limit: ADMIN_READ

8.6 PUT /api/admin/emi/tenures

Purpose

Replace the vocabulary. Periods must stay within 1…120; deactivate rather than delete a period still assigned to a bank.

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: Emi_UPDATE · Rate limit: ADMIN_WRITE

Error Cases

HTTPCodeCondition
400EMI_TENURE_NOT_IN_VOCABULARYA period outside 1…120 (or the replace list is otherwise invalid)
409EMI_TENURE_IN_USEDeleting a period still referenced by a bank

8.7–8.9 GET/POST/DELETE /api/admin/emi/eligibility/categories

Purpose

Select and deselect categories as eligible. A selected category confers EMI on all its descendants (tree expansion at read time).

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permissions: Emi_READ / Emi_CREATE / Emi_DELETE · Rate limit: ADMIN_READ / ADMIN_WRITE

Error Cases

HTTPCodeCondition
404EMI_CATEGORY_NOT_FOUNDNo such category
409EMI_CATEGORY_ALREADY_ELIGIBLEDuplicate selection

8.10–8.12 GET/POST/DELETE /api/admin/emi/eligibility/products

Purpose

Select and deselect individual products. Membership is the union of the category and product selections (when mode is selected).

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permissions: Emi_READ / Emi_CREATE / Emi_DELETE

Error Cases

HTTPCodeCondition
404EMI_PRODUCT_NOT_FOUNDNo such live product
409EMI_PRODUCT_ALREADY_ELIGIBLEDuplicate selection

8.13 GET /api/admin/emi/eligibility/effective-products

Purpose

The product list a customer would actually see — mirrors the customer gate: live status, sellingPrice > 0, and the current eligibility mode (expansion when selected; everything when entire_store).

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: Emi_READ · Rate limit: ADMIN_READ

8.14 GET /api/admin/emi/banks

Purpose

List banks with rates and tenures attached, sorted (displayOrder/name/createdAt — a closed set), paginated.

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: EmiBanks_READ · Rate limit: ADMIN_READ

8.15 POST /api/admin/emi/banks

Purpose

Create a bank. Name is NFC-normalised and unique among live banks.

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: EmiBanks_CREATE · Rate limit: ADMIN_WRITE

Error Cases

HTTPCodeCondition
409EMI_BANK_NAME_ALREADY_EXISTSDuplicate live name
400EMI_BANK_LOAN_RANGE_INVALIDmin > max
400EMI_BANK_LOGO_ALT_REQUIRES_KEYalt without a key

8.16 PATCH /api/admin/emi/banks/reorder

Purpose

Rewrite displayOrder from the supplied id order. All ids must exist and be live (404 otherwise). Transactional.

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: EmiBanks_UPDATE · Rate limit: ADMIN_WRITE

8.17 GET /api/admin/emi/banks/{bankId}

Purpose

One bank with its rate history and offered tenures.

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: EmiBanks_READ · Rate limit: ADMIN_READ

Error Cases

HTTPCodeCondition
404EMI_BANK_NOT_FOUNDMissing or soft-deleted

8.18 PATCH /api/admin/emi/banks/{bankId}

Purpose

Update name/logo/loan limits/display order/active flag.

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: EmiBanks_UPDATE · Rate limit: ADMIN_WRITE

Error Cases

HTTPCodeCondition
409EMI_BANK_NAME_ALREADY_EXISTSDuplicate live name
400EMI_BANK_LOAN_RANGE_INVALIDmin > max (cross-field CHECK — the DTO mirrors it, the DB enforces)
400EMI_BANK_LOGO_ALT_REQUIRES_KEYalt without a key

8.19 DELETE /api/admin/emi/banks/{bankId}

Purpose

Soft delete. A bank disappears from the customer catalog.

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: EmiBanks_DELETE · Rate limit: ADMIN_WRITE

Error Cases

HTTPCodeCondition
409EMI_BANK_INACTIVEActing on an already-deactivated bank
409referenced-rowRESTRICT refusal mapped via isReferencedRowError

8.20 POST /api/admin/emi/banks/{bankId}/restore

Purpose

Restore a soft-deleted bank.

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: EmiBanks_RESTORE · Rate limit: ADMIN_WRITE

8.21 GET /api/admin/emi/banks/{bankId}/rates

Purpose

The append-only rate history.

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: EmiBanks_READ · Rate limit: ADMIN_READ

8.22 POST /api/admin/emi/banks/{bankId}/rates

Purpose

Schedule a rate (annualRateBps 0…10000, effectiveFrom ISO instant). It becomes the quoted rate at effective_from. There is no PATCH — a rate is corrected by superseding it.

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: EmiBanks_CREATE · Rate limit: ADMIN_WRITE

Error Cases

HTTPCodeCondition
409EMI_RATE_EFFECTIVE_DATE_ALREADY_EXISTSSame effective date on a live row
400EMI_RATE_EFFECTIVE_DATE_OUT_OF_RANGEDate ≥ 2100
404EMI_BANK_NOT_FOUNDMissing bank

8.23 DELETE /api/admin/emi/banks/{bankId}/rates/{rateId}

Purpose

Cancel a scheduled rate (only while effective_from > now()). A rate that is or has been in force is refused — the record of what was quoted.

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: EmiBanks_DELETE · Rate limit: ADMIN_WRITE

Error Cases

HTTPCodeCondition
409EMI_RATE_IN_FORCE_NOT_DELETABLERate in force or past
409EMI_BANK_RATE_NOT_CONFIGUREDNo rate row at all

8.24 GET /api/admin/emi/banks/{bankId}/tenures

Purpose

The bank's offered periods.

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: EmiBanks_READ · Rate limit: ADMIN_READ

8.25 PUT /api/admin/emi/banks/{bankId}/tenures

Purpose

Replace the bank's offered periods wholesale. Every month must be in the vocabulary and active (deactivation blocks new assignments). Serialised with FOR UPDATE so two concurrent replaces cannot produce the union of their sets.

Auth and Permissions

  • Auth: JwtAuthGuard + RoleGuard · Permission: EmiBanks_UPDATE · Rate limit: ADMIN_WRITE

Error Cases

HTTPCodeCondition
400EMI_TENURE_NOT_IN_VOCABULARYA month is missing from the vocabulary or deactivated — the message names the period
404EMI_BANK_NOT_FOUNDMissing bank

9. Flow Diagrams

9.1 Route Ownership

9.2 Request Sequence (options)

9.3 Error Branch (calculate)

EndpointPagination TypeDefault SizeMax SizeSort FieldsFiltersResult Cap
GET /api/admin/emi/banksoffset page/size20100displayOrder / name / createdAt (closed set)none
GET /api/admin/emi/eligibility/effective-productsoffset page/size20100standardnone (mode drives the set)

11. Caching, Jobs, and External Integrations

IntegrationUsed?DetailsSource
Redis cacheYesemi:v1:settings (30s), emi:v1:eligible-categories (30s), emi:v1:banks (min(30s, next rate boundary)); nothing price-derived; cache-down → uncached reademi.constants.ts
BullMQNoNo queue exists by design
External APINoNo bank is contacted; references are never validated

13. Mandatory Deep API Documentation Pack

13.1 Route-by-Route Completeness Matrix

RouteController MethodDTOsService MethodGuardsPermissionsCacheJobsDB TouchesErrorsDocumented?
GET /optionsoptionsEmiProductParamsDto + EmiVariantQueryDtogetOptionsPublic+Throttlesettings/banks readproduct, variant, settings, banks404/400Yes
GET /calculatecalculateEmiProductParamsDto + CalculateEmiQueryDtocalculatePublic+Throttlesettings/banks readproduct, variant, settings, banks12 codesYes
GET /settingsgetgetJWT+Role+ThrottleEmi_READsettingsYes
PUT /settingsupdateUpdateEmiSettingsDtoupdateJWT+Role+ThrottleEmi_UPDATEinvalidate emisettings400Yes
GET /tenureslistlistJWT+Role+ThrottleEmi_READtenuresYes
PUT /tenuresreplaceReplaceEmiTenuresDtoreplaceJWT+Role+ThrottleEmi_UPDATEinvalidatetenures400/409Yes
GET/POST/DELETE /eligibility/categories3 methodscategory DTOseligibility serviceJWT+Role+ThrottleEmi_*invalidateselection tables404/409Yes
GET/POST/DELETE /eligibility/products3 methodsproduct DTOseligibility serviceJWT+Role+ThrottleEmi_*invalidateselection tables404/409Yes
GET /eligibility/effective-productslistEffectiveProductsquerysameJWT+Role+ThrottleEmi_READsettings, expansion, productsYes
GET /bankslistquerylistJWT+Role+ThrottleEmiBanks_READbanks+rates+tenuresYes
POST /bankscreateCreateEmiBankDtocreateJWT+Role+ThrottleEmiBanks_CREATEinvalidatebanks409/400Yes
PATCH /banks/reorderreorderReorderEmiBanksDtoreorderJWT+Role+ThrottleEmiBanks_UPDATEinvalidatebanks (tx)404Yes
GET /banks/{bankId}findOnefindOneJWT+Role+ThrottleEmiBanks_READbank+rates+tenures404Yes
PATCH /banks/{bankId}updateUpdateEmiBankDtoupdateJWT+Role+ThrottleEmiBanks_UPDATEinvalidatebank409/400Yes
DELETE /banks/{bankId}removeremoveJWT+Role+ThrottleEmiBanks_DELETEinvalidatebank409Yes
POST /banks/{bankId}/restorerestorerestoreJWT+Role+ThrottleEmiBanks_RESTOREinvalidatebank404Yes
GET /banks/{bankId}/rateslistrate service listJWT+Role+ThrottleEmiBanks_READrates404Yes
POST /banks/{bankId}/ratescreateCreateEmiBankRateDtorate service createJWT+Role+ThrottleEmiBanks_CREATEinvalidaterates409/400/404Yes
DELETE .../rates/{rateId}removerate service removeJWT+Role+ThrottleEmiBanks_DELETEinvalidaterates409Yes
GET /banks/{bankId}/tenureslistbank-tenure listJWT+Role+ThrottleEmiBanks_READtenures404Yes
PUT /banks/{bankId}/tenuresreplaceReplaceEmiBankTenuresDtobank-tenure replaceJWT+Role+ThrottleEmiBanks_UPDATEinvalidatelink rows (tx+lock)400/404Yes

13.2 Request/Response Exhaustiveness

Covered in §6 and §8: minimal and full payloads for every body endpoint, the two customer responses with real verified numbers (§8.1/8.2), every nullable field, per-endpoint error tables with the exact codes, and the uuid-v7 / tenure-integer validation notes.

13.3 API Diagram Pack

Route ownership (§9.1), options sequence (§9.2), calculate error tree (§9.3), plus the estimator and rate-boundary flows in the feature/backend docs.

13.4 Consumer Integration Notes

ConsumerRequired KnowledgeFailure HandlingContract Stability
Storefront (product detail)Call /options before rendering any EMI affordance; pass variantId once chosen, omit it for the pre-selection "from" estimate; render quotes as given — never recompute; basePrice echo detects repricing; never append an extra query param (campaign tag, cache-buster) — forbidNonWhitelisted turns it into a 400available: false → hide affordance (reason available); 404 → 404 the page; 404 EMI_VARIANT_NOT_FOUND → the variant vanished or belongs elsewhere, refetch the productStable
Storefront (calculate)bankId/tenureMonths from the options response; same variantId used for /options; uuid v7; tenure int 1…120; same no-extra-query-param rule403/409 → hide; 404 bank/variant → re-fetch options; 400 tenure → re-fetch optionsStable
Storefront (UI copy)isEstimate: true always; equal installments cannot exactly retire an amortising loan (final month absorbs residue)Stable
Admin (merchandiser)Emi_* grants; Emi_UPDATE carries the kill switch; 30s cache lag409 duplicate selectionsStable
Admin (rate-entry)EmiBanks_* grants; rates are append-only — no PATCH, future-only delete409 in-force delete → supersede insteadStable
QA24 error codes; exact arithmetic; 0% branch; offerability four conditionsReproduce via exact codesStable

13.5 API Tradeoffs and Rationale

DecisionChosen BehaviorAlternatives ConsideredWhy This TradeoffRiskMitigation
Server computes all quotesquotes in /optionsClient-side formulaBrowser float drift — the server is rightPayload sizenil (0.004ms/quote)
available: false instead of 4xxOptions never errors on ineligibility4xxDistinguish "no EMI" from "no product"
Coded errors on calculateSpecific codesGeneric 400Client has seen the offer; failure = change underneath
Tenure as query inttenureMonths=12body/pathSimple; DTO bounds the DoS exponent
uuid v7 requiredv4 rejectedaccept any uuidplatform conventionclient surprisedocumented
No PATCH on ratessupersede-onlyeditable raterecord of what was advertisedoperator must re-enterdocumented

13.6 API Change Impact

ChangeAffected ConsumersBackend ImpactData ImpactMigration Needed?Compatibility Plan
New bank/rate/tenure admin fieldsAdmin panel formsDTO/servicenoneNoAdditive
New eligibility modeAdmin + storefrontenum + servicenoneNoAdditive
Price-source list growsAdmin panelenumnoneNoAdditive (contract spec guards)
emi:v1: shape changenone (server-rendered quotes)cache prefix bumpnoneNobump to v2

14. Zero-Omission API Checklist

  • Every controller route is documented (§4, §8, §13.1).
  • Every parent route prefix and runtime URL is documented (§2, §4) — including the /api/mobile/emi/... and NO-/api/emi/... fact.
  • Every DTO field, enum, default, transform, and validator is documented (§6, §7).
  • Every response field, nullable field, and generated field is documented (§6.6, §8).
  • Every auth, guard, permission, and public branch is documented (§5).
  • Every success, validation, auth, not-found, conflict, and rate-limit branch is documented (§8).
  • Every database read/write, cache hit/miss/invalidation, queue job, and external call is documented (§8 side effects, §11).
  • Every route has examples for minimal request, success response, and representative failures (§8).
  • Every endpoint family has route, sequence, and error diagrams (§9).
  • Every tradeoff and compatibility risk is documented (§13.5, §13.6).
  • The API doc links to backend and features/flows (See Also).

15. Integration Checklist

  • Every route from controllers is documented (25/25).
  • Every DTO field is documented.
  • Every enum value is documented.
  • Every response envelope is documented.
  • Every error code is documented (25/25, including EMI_VARIANT_NOT_FOUND).
  • Every auth guard and permission is documented.
  • Every cache key, queue job, and external call is documented — no queue exists by design.
  • Every diagram matches the current code.
  • The API doc links to backend and features/flows.

See Also

  • Backend doc: /docs/developer/emi/backend
  • Features and flows doc: /docs/developer/emi/feature
  • TDD: not yet published

On this page

EMI Calculator - API Reference1. Documentation Evidence2. Module Summary3. Concepts and Terminology4. API Surface Map5. Auth, Identity, and Permissions6. DTO and Model Reference6.1 EmiProductParamsDto — customer routes6.2 EmiVariantQueryDto/options, and CalculateEmiQueryDto/calculate6.3 UpdateEmiSettingsDtoPUT /settings6.4 Bank DTOs6.5 Rate DTO6.6 Response DTOs7. Enum Reference8. Endpoint Reference8.1 GET /api/mobile/emi/products/{productId}/optionsPurposeAuth and PermissionsResponseSide EffectsError Cases8.2 GET /api/mobile/emi/products/{productId}/calculatePurposeAuth and PermissionsRequestResponseSide EffectsError Cases8.3 GET /api/admin/emi/settingsPurposeAuth and Permissions8.4 PUT /api/admin/emi/settingsPurposeAuth and PermissionsSide EffectsError Cases8.5 GET /api/admin/emi/tenuresPurposeAuth and Permissions8.6 PUT /api/admin/emi/tenuresPurposeAuth and PermissionsError Cases8.7–8.9 GET/POST/DELETE /api/admin/emi/eligibility/categoriesPurposeAuth and PermissionsError Cases8.10–8.12 GET/POST/DELETE /api/admin/emi/eligibility/productsPurposeAuth and PermissionsError Cases8.13 GET /api/admin/emi/eligibility/effective-productsPurposeAuth and Permissions8.14 GET /api/admin/emi/banksPurposeAuth and Permissions8.15 POST /api/admin/emi/banksPurposeAuth and PermissionsError Cases8.16 PATCH /api/admin/emi/banks/reorderPurposeAuth and Permissions8.17 GET /api/admin/emi/banks/{bankId}PurposeAuth and PermissionsError Cases8.18 PATCH /api/admin/emi/banks/{bankId}PurposeAuth and PermissionsError Cases8.19 DELETE /api/admin/emi/banks/{bankId}PurposeAuth and PermissionsError Cases8.20 POST /api/admin/emi/banks/{bankId}/restorePurposeAuth and Permissions8.21 GET /api/admin/emi/banks/{bankId}/ratesPurposeAuth and Permissions8.22 POST /api/admin/emi/banks/{bankId}/ratesPurposeAuth and PermissionsError Cases8.23 DELETE /api/admin/emi/banks/{bankId}/rates/{rateId}PurposeAuth and PermissionsError Cases8.24 GET /api/admin/emi/banks/{bankId}/tenuresPurposeAuth and Permissions8.25 PUT /api/admin/emi/banks/{bankId}/tenuresPurposeAuth and PermissionsError Cases9. Flow Diagrams9.1 Route Ownership9.2 Request Sequence (options)9.3 Error Branch (calculate)10. Pagination, Sorting, Filtering, and Search11. Caching, Jobs, and External Integrations13. Mandatory Deep API Documentation Pack13.1 Route-by-Route Completeness Matrix13.2 Request/Response Exhaustiveness13.3 API Diagram Pack13.4 Consumer Integration Notes13.5 API Tradeoffs and Rationale13.6 API Change Impact14. Zero-Omission API Checklist15. Integration ChecklistSee Also