Credit API — endpoints, permissions, shapes (Task 10.2)

Status: ✅ Complete

Plan reference: Task 10.2 in the Credit module implementation plan (operations workspace root)

All credit endpoints are namespaced under /credit. They cover facility CRUD + lifecycle transitions, payments, configuration, and reporting. For the underlying tables see credit data model; for the math behind :ref/schedule see credit engine.

Permissions

Every endpoint is gated by one of these permissions:

PermissionScope
credit:viewRead facilities, schedules, payments, reports
credit:createCreate a facility and submit for review
credit:reviewMaker-checker review (approve to Shariah / reject)
credit:approveShariah/credit approval, mark asset owned, activate, non-accrual
credit:record_paymentRecord payments and early settlements
credit:config_manageManage the posting account-map

Facilities — CRUD + lifecycle

Method + pathPermissionPurpose
POST /credit/facilitiescredit:createCreate a draft facility
GET /credit/facilitiescredit:viewList / filter facilities
GET /credit/facilities/:refcredit:viewFetch one facility by CF-YYYYMMDD-XXXX ref
GET /credit/facilities/:ref/schedulecredit:viewThe generated repayment schedule
POST /credit/facilities/:ref/reviewcredit:createSubmit draft → pending_review
POST /credit/facilities/:ref/approvecredit:review / credit:approveAdvance review → shariah_review → approved
POST /credit/facilities/:ref/rejectcredit:reviewpending_review/shariah_review → rejected
POST /credit/facilities/:ref/asset-ownedcredit:approveMark the asset acquired/owned
POST /credit/facilities/:ref/activatecredit:approveVR-001 gate + atomic origination journal → active

POST /credit/facilities — create

// request
{
  "customerId": "cus_123",
  "productType": "murabaha",          // | "ijarah" | "diminishing_musharakah"
  "assetCost": "100000000.0000",
  "downPayment": "0.0000",
  "rate": 8,                           // annual %, used by DM/Ijarah engine
  "termPeriods": 15,
  "periodsPerYear": 1,
  "fixedTotalProfit": "20000000.0000", // REQUIRED for murabaha; omit for DM/ijarah
  "asset": { "description": "Toyota Hilux fleet", "cost": "100000000.0000" }
}
// response
{
  "facilityRef": "CF-20260615-0042",
  "status": "draft",
  "facilityAmount": "100000000.0000"
}

Validation rejects a Murabaha facility with no fixedTotalProfit, and rejects DM/Ijarah facilities that supply one (their profit is rate-derived).

GET /credit/facilities/:ref/schedule

Returns the per-period plan from credit_schedule_lines:

{
  "facilityRef": "CF-20260615-0042",
  "lines": [
    {
      "period": 1,
      "paymentDate": "2027-06-15",
      "beginningBalance": "100000000.0000",
      "principalPortion": "6666666.6700",
      "profitPortion": "8000000.0000",
      "totalPayment": "14666666.6700",
      "endingBalance": "93333333.3300",
      "ethicaOwnershipPct": "93.33",
      "customerOwnershipPct": "6.67",
      "status": "pending"
    }
    // ... period 2..15
  ]
}

POST /credit/facilities/:ref/activate

Enforces VR-001 (asset must be owned_by_ethica for Murabaha/Ijarah or co_owned for DM) and atomically posts the origination journal. On a failed gate it returns 409 Conflict; on an unmapped account the posting fails closed.

// response
{ "status": "active", "originationJournalId": "je_88f1", "activatedAt": "2026-06-15T10:00:00Z" }

Payments + servicing

Method + pathPermissionPurpose
POST /credit/facilities/:ref/paymentscredit:record_paymentRecord a payment (incl. partial / extra / early settlement)
GET /credit/facilities/:ref/paymentscredit:viewList payments on a facility
POST /credit/facilities/:ref/overduecredit:approveTrigger overdue evaluation (also run by the daily job)
POST /credit/facilities/:ref/non-accrualcredit:approveFlag the facility non-accrual

POST /credit/facilities/:ref/payments

// request
{
  "amount": "14666666.6700",
  "paymentDate": "2027-06-15",
  "reference": "TRX-99812",
  "earlySettlement": false   // when true: DM/Ijarah waive future rent;
                             //   Murabaha recognises all remaining deferred profit
}
// response
{
  "paymentId": "pay_55",
  "journalEntryId": "je_91c2",
  "appliedTo": [{ "period": 1, "status": "paid", "paidAmount": "14666666.6700" }],
  "extraPayment": false
}

While a facility is non-accrual, the payment posts Dr cash / Cr principal only — no income is recognised.

Config — posting account-map

Method + pathPermissionPurpose
GET /credit/config/account-mapcredit:config_manage / credit:viewRead the product × event × side → GL map
PUT /credit/config/account-mapcredit:config_manageReplace/update mappings
// PUT request
{
  "mappings": [
    { "productType": "murabaha", "eventType": "origination", "side": "debit",  "glAccount": "1300" },
    { "productType": "murabaha", "eventType": "origination", "side": "credit", "glAccount": "1001" },
    { "productType": "murabaha", "eventType": "origination", "side": "credit", "glAccount": "2400" }
  ]
}

If a required mapping is missing at posting time the engine fails closed rather than defaulting an account.

Reports

Method + pathPermissionPurpose
GET /credit/reports/portfoliocredit:viewOutstanding by product / status
GET /credit/reports/incomecredit:viewIncome contribution from the credit book
GET /credit/reports/shariah-compliancecredit:viewShari'ah-compliance summary

Files

  • Controller — ethica-api/src/credit/credit.controller.ts
  • Service — ethica-api/src/credit/credit.service.ts
  • DTOs — ethica-api/src/credit/dto/*.dto.ts