> For the complete documentation index, see [llms.txt](https://midatechnologies.gitbook.io/mida-developer-portal/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://midatechnologies.gitbook.io/mida-developer-portal/midaforms/decision-matrices.md).

# Decision matrices (staff)

Decision matrices API reference

Base path: `/decision-matrices`

Tag: Decision Matrices

Auth: Bearer JWT + permissions.

### 7.1 Create

```
POST /decision-matrices
```

Permission: Create Auto Decisions

**Request body** (`CreateDecisionMatrixDto`)

| Field            | Type                     | Required | Notes                                                               |
| ---------------- | ------------------------ | -------- | ------------------------------------------------------------------- |
| `name`           | string (3–100)           | Yes      | Unique per organisation                                             |
| `productId`      | UUID                     | Yes      | Active product for the org                                          |
| `isActive`       | boolean                  | No       | Default `true` (may be forced inactive if workflow publish pending) |
| `authoringMode`  | `FREE_TEXT` \| `CATALOG` | No       | Default `FREE_TEXT`                                                 |
| `workflowId`     | UUID                     | No       | Decisioning publish workflow                                        |
| `organizationId` | string                   | No       | Mida only — create for another org                                  |
| `scoringRules`   | ScoringRuleDto\[]        | Yes      | Weights should sum to 100                                           |
| `decisionRules`  | DecisionRuleDto\[]       | Yes      | Score bands                                                         |

**ScoringRuleDto**

| Field                     | FREE\_TEXT       | CATALOG                          |
| ------------------------- | ---------------- | -------------------------------- |
| `parentVariable`          | Required         | Optional (resolved from catalog) |
| `parentVariableCatalogId` | Forbidden        | Required                         |
| `weight`                  | Required (0–100) | Required                         |
| `subVariables`            | Required array   | Required array                   |

**SubVariableDto**

| Field                  | FREE\_TEXT       | CATALOG                              |
| ---------------------- | ---------------- | ------------------------------------ |
| `name`                 | Required         | Optional (resolved)                  |
| `condition`            | Required         | Optional denormalized                |
| `score`                | Required (1–100) | Required (1–100)                     |
| `subvariableCatalogId` | Forbidden        | Required                             |
| `operator`             | Forbidden        | Required                             |
| `value`                | —                | For `EQ`/`NEQ`/`LT`/`LTE`/`GT`/`GTE` |
| `min` / `max`          | —                | For `BETWEEN`                        |
| `values`               | —                | For `IN`                             |

**DecisionRuleDto**

| Field                   | Type           | Required | Notes                 |
| ----------------------- | -------------- | -------- | --------------------- |
| `label`                 | DecisionLabel  | Yes      |                       |
| `minScore` / `maxScore` | number 0–100   | Yes      | Band                  |
| `decision`              | DecisionType   | Yes      |                       |
| `amountApproved`        | number 0–100   | Yes      | % of requested amount |
| `tenureApproved`        | number ≥ 0     | No       | Months                |
| `action`                | DecisionAction | Yes      |                       |

**FREE\_TEXT example**

```json
{
  "name": "Retail Individual Matrix",
  "productId": "11111111-1111-4111-8111-111111111111",
  "authoringMode": "FREE_TEXT",
  "isActive": true,
  "scoringRules": [
    {
      "parentVariable": "Bank Relationship",
      "weight": 20,
      "subVariables": [
        { "name": "Account Age", "condition": ">= 12 months", "score": 8 }
      ]
    },
    {
      "parentVariable": "Demographic Analysis",
      "weight": 20,
      "subVariables": [
        { "name": "Age", "condition": "25-45", "score": 9 }
      ]
    },
    {
      "parentVariable": "Income",
      "weight": 20,
      "subVariables": [
        { "name": "Monthly Income", "condition": ">= 100000", "score": 8 }
      ]
    },
    {
      "parentVariable": "DTI",
      "weight": 15,
      "subVariables": [
        { "name": "DTI Ratio", "condition": "<= 40%", "score": 7 }
      ]
    },
    {
      "parentVariable": "Contact Details",
      "weight": 10,
      "subVariables": [
        { "name": "Phone Count", "condition": ">= 1", "score": 6 }
      ]
    },
    {
      "parentVariable": "Character",
      "weight": 10,
      "subVariables": [
        { "name": "Performing Accounts", "condition": ">= 1", "score": 7 }
      ]
    },
    {
      "parentVariable": "Residual Balance",
      "weight": 5,
      "subVariables": [
        { "name": "Residual", "condition": ">= 20000", "score": 5 }
      ]
    }
  ],
  "decisionRules": [
    {
      "label": "PRIME",
      "minScore": 70,
      "maxScore": 100,
      "decision": "APPROVE",
      "amountApproved": 100,
      "tenureApproved": 12,
      "action": "AUTO_DISBURSE"
    },
    {
      "label": "NEAR_PRIME",
      "minScore": 50,
      "maxScore": 69,
      "decision": "REVIEW",
      "amountApproved": 70,
      "tenureApproved": 6,
      "action": "SEND_TO_UNDERWRITER"
    },
    {
      "label": "HIGH_RISK",
      "minScore": 0,
      "maxScore": 49,
      "decision": "DECLINE",
      "amountApproved": 0,
      "tenureApproved": 0,
      "action": "NOTIFY_APPLICANT"
    }
  ]
}
```

Known FREE\_TEXT parent names used by the scorer:

* Bank Relationship
* Demographic Analysis
* Income
* DTI
* Contact Details
* Character
* Residual Balance
* Transactions/Inflow Count

**CATALOG example**

```json
{
  "name": "Retail Catalog Matrix",
  "productId": "11111111-1111-4111-8111-111111111111",
  "authoringMode": "CATALOG",
  "isActive": true,
  "workflowId": "22222222-2222-4222-8222-222222222222",
  "scoringRules": [
    {
      "parentVariableCatalogId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
      "weight": 40,
      "subVariables": [
        {
          "subvariableCatalogId": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
          "operator": "BETWEEN",
          "min": 18,
          "max": 45,
          "score": 8
        },
        {
          "subvariableCatalogId": "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
          "operator": "GTE",
          "value": 100000,
          "score": 7
        }
      ]
    },
    {
      "parentVariableCatalogId": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
      "weight": 60,
      "subVariables": [
        {
          "subvariableCatalogId": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
          "operator": "IN",
          "values": ["SALARIED", "SELF_EMPLOYED"],
          "score": 6
        }
      ]
    }
  ],
  "decisionRules": [
    {
      "label": "PRIME",
      "minScore": 70,
      "maxScore": 100,
      "decision": "APPROVE",
      "amountApproved": 100,
      "tenureApproved": 12,
      "action": "AUTO_DISBURSE"
    }
  ]
}
```

**Response data** (create / get by id)

Full matrix with nested rules — see the source Google Doc for the complete nested example. Get-by-id also includes `publishReviews` when present.

### 7.2 List

```
GET /decision-matrices
```

Permission: View Auto Decision

**Query** (`DecisionMatrixFilterDto`)

| Param                   | Type        | Required | Notes               |
| ----------------------- | ----------- | -------- | ------------------- |
| `organizationId`        | string      | No       | Mida only           |
| `search`                | string      | No       | Name contains       |
| `status`                | boolean     | No       | `isActive`          |
| `publishStatus`         | enum        | No       |                     |
| `productId`             | string      | No       |                     |
| `startDate` / `endDate` | date string | No       | `createdAt` range   |
| `page`                  | number      | No       | Default 1           |
| `limit`                 | number      | No       | Default 10, max 100 |

### 7.3 Get by id

```
GET /decision-matrices/:id
```

Permission: View Auto Decision

Response data is the full matrix object as in create response (§7.1), plus `publishReviews` when applicable.

### 7.4 Update

```
PUT /decision-matrices/:id
```

Permission: Edit Auto Decision

Request body (`UpdateDecisionMatrixDto`) is `PartialType(CreateDecisionMatrixDto)` — all create fields optional.

**Important rules**

* Changing `authoringMode` requires sending a full new `scoringRules` set for that mode.
* Do not mix FREE\_TEXT and CATALOG fields for the active mode.
* Matrices with a publish workflow typically reset toward draft and re-submit for publish after update (see workflow doc).

### 7.5 Other matrix endpoints (summary)

| Method   | Path                                                                 | Permission               | Body / notes                                         |
| -------- | -------------------------------------------------------------------- | ------------------------ | ---------------------------------------------------- |
| `GET`    | `/decision-matrices/stats`                                           | View                     | Org totals: total / active / applications decisioned |
| `GET`    | `/decision-matrices/application-score/:appId`                        | View                     | Internal ApplicationScoring rows for a Forms app     |
| `GET`    | `/decision-matrices/application-run/:appId/run`                      | Create                   | Run internal auto-decision for a Forms submission    |
| `PUT`    | `/decision-matrices/:id`                                             | Edit                     | Update (§7.4)                                        |
| `PATCH`  | `/decision-matrices/:id/toggle-status`                               | Edit                     | `{ "isActive": true \| false }`                      |
| `DELETE` | `/decision-matrices/:id`                                             | Auth                     | Soft/hard delete per service rules                   |
| `PATCH`  | `/decision-matrices/attach-matrix-to-form/:formId/:decisionMatrixId` | Create                   | Attach matrix to form (same product)                 |
| `DELETE` | `/decision-matrices/:matrixId/forms/:formId`                         | Edit                     | Detach form                                          |
| `POST`   | `/decision-matrices/:id/attach-workflow`                             | Edit                     | `{ "workflowId": "…" }`                              |
| `DELETE` | `/decision-matrices/:id/workflow`                                    | Edit                     | Detach workflow                                      |
| `POST`   | `/decision-matrices/:id/submit-for-publish`                          | Edit                     | Optional `{ "note": "..." }`                         |
| `POST`   | `/decision-matrices/:id/publish-review`                              | Create Approval Workflow | `{ "action": "...", "comment": "..." }`              |

## 8. FREE\_TEXT vs CATALOG at runtime

External evaluate does not take an authoring-mode flag.

1. Client sends `decisionMatrixId`.
2. Backend loads the matrix (`isActive: true`, matching `orgKey`).
3. `calculateAutoDecisionScore` branches on `decisionMatrix.authoringMode`:
   * `FREE_TEXT` → legacy parent-name switch + string condition parsers
   * `CATALOG` → catalog code metrics + structured operator / `conditionValue`

So which path runs is entirely determined by how the matrix was authored.

## 9. Error cases

| Scenario                                               | Typical status |
| ------------------------------------------------------ | -------------- |
| Invalid DTO / missing `loan.amount`                    | 400            |
| Unknown / inactive matrix for org                      | 404            |
| Midax analyze failure                                  | 400            |
| Unknown bank `analysisId` on get/callback              | 404            |
| Unknown external decisioning result id                 | 404            |
| Rerun missing stored `accountId`                       | 400            |
| Non-Mida user passes `organizationId` on list matrices | 403            |
| FREE\_TEXT payload with catalog IDs (or reverse)       | 400            |
| CATALOG missing operator / value shape                 | 400            |

## 10. Quick checklist

**Staff (Forms UI / API)**

* Create product
* Create matrix (`FREE_TEXT` or `CATALOG`) with weights = 100
* Ensure matrix is active / published as required by org workflow
* Copy `decisionMatrixId` for external clients

**External client — bank statement (optional standalone)**

* `POST /external-bank-statement/analyze` with `orgKey`, statement URL, `callbackUrl`
* Receive Midax callback on your URL or poll `GET /external-bank-statement/:analysisId`

**External client — decisioning**

* Ensure Midax has completed bank-statement analysis for the Mono/`accountId`
* `POST /external-decisioning/evaluate` with `orgKey`, matrix id, `applicationData.loan.amount`, `bankStatement.accountId`
* Use `referenceId` for idempotent retries
* Use `POST /external-decisioning/:id/rerun` after matrix updates
* List / filter with `GET /external-decisioning?orgKey=...`

## Endpoint index

| Method   | Path                                   | Auth                 |
| -------- | -------------------------------------- | -------------------- |
| `POST`   | `/external-bank-statement/analyze`     | Public               |
| `GET`    | `/external-bank-statement/:analysisId` | Public               |
| `POST`   | `/webhooks/external-bank-statement`    | Public (Midax→Forms) |
| `POST`   | `/external-decisioning/evaluate`       | Public               |
| `GET`    | `/external-decisioning`                | Public               |
| `GET`    | `/external-decisioning/:id`            | Public               |
| `POST`   | `/external-decisioning/:id/rerun`      | Public               |
| `POST`   | `/decision-matrices`                   | JWT                  |
| `GET`    | `/decision-matrices`                   | JWT                  |
| `GET`    | `/decision-matrices/:id`               | JWT                  |
| `PUT`    | `/decision-matrices/:id`               | JWT                  |
| `PATCH`  | `/decision-matrices/:id/toggle-status` | JWT                  |
| `DELETE` | `/decision-matrices/:id`               | JWT                  |
