# NIC IMIS — Marine Insurance API · Integration Guide (for AI coding agents)

> Give this file to an AI coding assistant (Claude, Copilot, Cursor, etc.) as
> context before asking it to build a client library, a webhook receiver, or
> a marine-policy issuance flow against the NIC IMIS Marine Insurance API.
> This is the source of truth for the integration contract.

---

## 1. What this API is

The **NIC IMIS Marine Insurance API** is a partner-facing REST surface exposed
by the National Insurance Corporation of Tanzania's IMIS platform, scoped to
the **Marine line of business** (Marine Cargo, Marine Hull and Machinery, etc).

A third-party integrator submits a marine insurance proposal through this API;
IMIS then runs the standard underwriting flow (premium calculation → approval
workflow → GePG control number → payment → TIRA submission → policy
commencement) and pushes status updates back to the partner-supplied callback
URLs along the way.

Non-marine products are not addressable through this API. If your integration
needs the full IMIS product catalogue across every line of business, use the
**General Public Open API** instead.

## 2. Base URLs

Every endpoint is mounted under `/{environment}/mware/marine/v1/`.

| Environment  | Base URL                                                   |
|--------------|------------------------------------------------------------|
| UAT          | `https://imis.nicinsurance.co.tz/uat/mware/marine/v1`      |
| Production   | `https://imis.nicinsurance.co.tz/production/mware/marine/v1` |

All calls are JSON over HTTPS. The spec is OpenAPI 3.0.

## 3. Authentication

Every request must include both of the following headers.

| Header        | Description                                                              |
|---------------|--------------------------------------------------------------------------|
| `API-KEY`     | The bcrypt-hashed API key issued to the partner (opaque string).         |
| `API-SECRETE` | The matching secret. **Note the spelling** — three-letter `SECRETE`.     |

- Keys are issued by NIC operations against a scoped **integration record**
  (see §9 — Integration scope).
- The plaintext key/secret pair is shown **once** at issue or rotation time —
  store both immediately in a secret manager.
- Anonymous (missing headers) → HTTP 401. Wrong headers → HTTP 401.
- Scoped-out integration → HTTP 403 with a machine-readable error code.

## 4. Endpoints (contract summary)

### 4.1 `GET /products/`

Returns approved, non-rider marine products.

**Response 200**

```json
{
  "status": true,
  "count": 2,
  "results": [
    {
      "id": 220,
      "code": "MC01",
      "name": "Marine Cargo",
      "description": "Covers cargo in transit by sea, air, road or rail.",
      "tira_code": "MC",
      "policy_term": 1,
      "policy_term_unit": "years",
      "vat_apply": true,
      "line_business": { "id": 5, "code": "LB04", "name": "Marine Insurance" },
      "product_class": { "id": 23, "code": "MC01", "name": "Marine Cargo" },
      "form_url": "https://imis.nicinsurance.co.tz/uat/mware/marine/v1/products/220/form/"
    }
  ]
}
```

### 4.2 `GET /products/{product_id}/form/`

Returns the dynamic form schema the partner must populate when submitting a
proposal for this product. Field `api_tag` values are the JSON keys you must
use in the `answers` block of the POST body.

**Response 200 (abbreviated)**

```json
{
  "status": true,
  "product": {
    "id": 220,
    "code": "MC01",
    "name": "Marine Cargo",
    "description": "…",
    "tira_code": "MC"
  },
  "submit_url": "https://imis.nicinsurance.co.tz/uat/mware/marine/v1/proposals/",
  "sections": [
    {
      "name": "Cargo",
      "fields": [
        {
          "api_tag": "cargo_value",
          "label": "Cargo value (TZS)",
          "type": "number",
          "required": true
        },
        {
          "api_tag": "voyage_from",
          "label": "Voyage from",
          "type": "text",
          "required": true
        },
        {
          "api_tag": "voyage_to",
          "label": "Voyage to",
          "type": "text",
          "required": true
        }
      ]
    }
  ]
}
```

**Rules:**
- Every field with `required: true` must be present in the POST body.
- For `type: "select"`, `type: "radio"`, and `type: "checkbox"`, the submitted
  value must match one of the `options` **exactly** (case-sensitive).
- `type: "number"` accepts floats. `type: "date"` expects ISO-8601 (`YYYY-MM-DD`).

### 4.3 `POST /proposals/`

Submits a proposal. This is the load-bearing endpoint.

**Request body**

```json
{
  "partner_reference": "PARTNER-MC-001",
  "product_id": 220,
  "answers": {
    "cargo_value": 15000000,
    "voyage_from": "Dar es Salaam",
    "voyage_to": "Mombasa"
  },
  "callbacks": {
    "control_number":     "https://partner.example.com/imis/cn",
    "approval_status":    "https://partner.example.com/imis/approval",
    "approval_decision":  "https://partner.example.com/imis/decision",
    "payment":            "https://partner.example.com/imis/payment",
    "tira_status":        "https://partner.example.com/imis/tira",
    "commencement":       "https://partner.example.com/imis/commencement"
  }
}
```

**Rules:**
- `partner_reference` is any string you choose to correlate the submission
  back to your own system. It's echoed on every callback.
- `product_id` must be a currently-eligible marine product id (see §4.1).
- `answers` keys must match `api_tag` values from §4.2. Any missing required
  field returns HTTP 400.
- `callbacks` is a JSON object. Any subset is allowed; missing channels are
  simply never called.

**Response 201**

```json
{
  "status": true,
  "code": "MARINE_API_PROPOSAL_RECEIVED",
  "message": "Proposal received; entering approval flow",
  "submission_id": 1108,
  "partner_reference": "PARTNER-MC-001",
  "submit_status": "PENDING_APPROVAL"
}
```

**Common error codes** (HTTP 400 unless stated):
- `MARINE_API_MISSING_FIELD` — required field absent from `answers`
- `MARINE_API_INVALID_OPTION` — value not in the field's `options`
- `MARINE_API_UNKNOWN_PRODUCT` — `product_id` not found (HTTP 404)
- `MARINE_API_PRODUCT_NOT_MARINE` — product exists but is not in the Marine LOB
- `MARINE_API_PRODUCT_NOT_ELIGIBLE` — product exists but is not API-eligible
- `MARINE_API_FORBIDDEN` — scope disables this endpoint (HTTP 403)
- `MARINE_API_INTERNAL_ERROR` — server-side failure (HTTP 500)

### 4.4 `GET /proposals/{partner_reference}/`

Polls the current state of a submission.

**Response 200**

```json
{
  "status": true,
  "id": 1108,
  "partner_reference": "PARTNER-MC-001",
  "submit_status": "TIRA_SUBMITTED",
  "cn_ack": true,
  "approval_status_ack": true,
  "approval_decision_ack": true,
  "pay_ack": true,
  "tira_ack": false,
  "commencement_ack": false,
  "last_error": null,
  "control_number": "992123456789",
  "premium": 145000.00,
  "policy_number": null
}
```

`submit_status` cycles through:
`RECEIVED` → `PENDING_APPROVAL` → `APPROVED` / `REJECTED` → `BILLED` → `PAID`
→ `TIRA_SUBMITTED` / `TIRA_FAILED` → `COMMENCED` / `FAILED`.

## 5. Callbacks (webhooks)

When a proposal moves through a lifecycle state, IMIS POSTs JSON to the
partner-supplied URL for that state. Delivery is fire-and-forget with a
configurable timeout (default 5s). Every attempt is recorded in the IMIS
audit log.

Each callback body is wrapped as:

```json
{
  "submission_id": 1108,
  "partner_reference": "PARTNER-MC-001",
  "kind": "CONTROL_NUMBER",
  "... kind-specific fields": "..."
}
```

Partners **must respond `2xx`** to acknowledge receipt. Non-2xx responses
leave the corresponding `*_ack` flag false; the dispatcher will retry the
callback the next time the same state transition fires. There is no
automatic backoff (Celery is disabled in this deployment).

### 5.1 Callback kinds

| `kind`               | Fired when                                                | Extra fields                                                                                     |
|----------------------|-----------------------------------------------------------|--------------------------------------------------------------------------------------------------|
| `CONTROL_NUMBER`     | GePG issues a control number                              | `control_number`, `tax_invoice_document_url`                                                    |
| `APPROVAL_STATUS`    | Every workflow stage transition (queued/reviewing/etc)    | `stage`, `status`, `comment`                                                                     |
| `APPROVAL_DECISION`  | Final approval / rejection                                | `decision` (`APPROVED` or `REJECTED`), `comment`                                                 |
| `PAYMENT`            | Payment cleared through GePG                              | `receipt_document_url`, `paid_amount`, `paid_at`                                                 |
| `TIRA_STATUS`        | TIRA cover-note submission result                         | `status` (`SUBMITTED` / `FAILED`), `cover_note`, `sticker_number`, `errors` (on failure)         |
| `COMMENCEMENT`       | Policy commencement finalised                             | `policy_number`, `policy_start_date`, `policy_end_date`, `policy_document_url`, `covernote_document_url` |

## 6. Recommended client patterns

### 6.1 Bootstrapping

1. Call `GET /products/` once at startup. Cache the product ids you plan to sell.
2. For each product you plan to sell, call `GET /products/{id}/form/` and
   cache the section/field schema. Persist the `api_tag` values in your
   client-side form builder.

### 6.2 Submitting

1. Build an `answers` dict keyed by `api_tag`.
2. POST `/proposals/` with a stable `partner_reference` (e.g. a UUID you also
   store on your side).
3. Persist the returned `submission_id`.

### 6.3 Following the lifecycle

You have two options — **pick one, not both**:

- **Callbacks (recommended)**: expose six HTTPS endpoints, one per callback
  kind, and register them in the `callbacks` field of the POST body.
- **Polling**: call `GET /proposals/{partner_reference}/` every 30–60 seconds
  until `commencement_ack` becomes true or `submit_status` becomes `FAILED`
  or `REJECTED`.

## 7. Error handling

- Retry `5xx` responses with exponential backoff (starting at ~1s, capping
  at ~30s, giving up after ~5 minutes).
- Do **not** retry `4xx` — treat as terminal, log, and surface to the operator.
- Both `4xx` and `5xx` return the same JSON shape:
  ```json
  { "status": false, "code": "MARINE_API_...", "message": "…", "errors": [...] }
  ```
  Log `code` in preference to `message` — codes are stable, messages are not.

## 8. Data hygiene

- Never store the API secret in source control. Use environment variables
  or a secret manager. On rotate, both the key and the secret change.
- Consider signing every callback receiver behind a shared HMAC secret you
  agreed with NIC ops out-of-band.
- All timestamps in requests and responses are naive local time
  (**Africa/Dar_es_Salaam**, UTC+3). No `USE_TZ`.

## 9. Integration scope

Every partner credential is bound to an **integration scope** managed by
NIC operators. A scope pins the credential to one or both surfaces (Marine,
General).

Effects the client will see:

- **Surface disabled**: `HTTP 403` on every endpoint.
- **Revoked**: `HTTP 401` on every endpoint.

Operators can also **rotate** credentials at any time; the old pair is
deactivated within the same request. Partners must adopt the new pair
provided by NIC ops.

## 10. Getting credentials

Contact **NIC ICT Team** (<ictsupport@nicinsurance.co.tz>) with:

1. The company name and technical contact.
2. The intended use — describe the product/flow you are integrating.
3. The API surface required (`Marine Insurance API` — this doc).
4. The **callback URLs** you will expose (one per callback kind).

Ops will provision a credential pair, hand you the plaintext exactly once,
and lock the integration to the agreed scope. If lost, the pair is rotated —
never recovered.

## 11. Discovery artefacts

The following are served publicly (no credentials needed) and are safe to
include verbatim in an AI agent's context window:

| Artefact                | URL                                                                                     |
|-------------------------|-----------------------------------------------------------------------------------------|
| Interactive Swagger UI  | `https://imis.nicinsurance.co.tz/uat/mware/marine/v1/docs/`                             |
| Raw OpenAPI 3.0 (YAML)  | `https://imis.nicinsurance.co.tz/uat/mware/marine/v1/openapi.yaml`                      |
| PDF reference           | `https://imis.nicinsurance.co.tz/uat/mware/marine/v1/docs.pdf`                          |
| Postman collection      | `https://imis.nicinsurance.co.tz/uat/mware/marine/v1/postman_collection.json`           |
| Postman environment     | `https://imis.nicinsurance.co.tz/uat/mware/marine/v1/postman_environment.json`          |

## 12. Prompt-ready snippet for an AI coding agent

Copy the block below into an AI assistant along with this guide:

> You are integrating a client to the **NIC IMIS Marine Insurance API**.
> The API contract is described in the attached Markdown. Build a strongly
> typed client (language: <your choice>) that:
>
> 1. Reads `API_KEY` and `API_SECRET` from environment variables.
> 2. Exposes `list_products()`, `get_product_form(product_id)`,
>    `submit_proposal(payload)`, and `poll_status(partner_reference)`.
> 3. Retries `5xx` responses with exponential backoff, never retries `4xx`.
> 4. Exposes an HTTP receiver server (e.g. FastAPI / Express / ASP.NET) with
>    six routes, one per callback kind. Each route logs the payload, marks
>    the corresponding state in a local DB, and returns `HTTP 200 {"ok":true}`.
> 5. Never logs the `API_SECRET` value.
>
> Base URL: `https://imis.nicinsurance.co.tz/uat/mware/marine/v1`.
> Headers: `API-KEY: ${API_KEY}` and `API-SECRETE: ${API_SECRET}` on
> every request (note the "SECRETE" spelling — three letters). All
> requests and responses are JSON.
