openapi: 3.0.3
info:
  title: NIC Marine Insurance Third-Party API
  version: "1.0.0"
  description: |
    REST API for third-party partners to integrate Marine insurance policies
    with NIC IMIS.

    A partner submits a 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.

    All endpoints are mounted under `/{environment}/mware/marine/v1/` where
    `environment` is `uat` or `production`.

    ## Integration scope

    Every partner credential is bound to an integration scope managed by NIC operators.
    Requests authenticated with a credential whose scope does not enable the Marine API
    are rejected with HTTP 403. Revoked integrations are similarly rejected. Operators
    can rotate credentials at any time from the mware Integrations dashboard.

    ## Authentication

    Every request must include both of these headers:

    | Header        | Description                                          |
    | ------------- | ---------------------------------------------------- |
    | `API-KEY`     | The bcrypt-hashed API key issued to the partner.     |
    | `API-SECRETE` | The matching secret (note the spelling).             |

    Keys are issued by NIC operations through the IMIS admin (`/uat/api/v1/refresh_api_key/`).
    The token TTL is controlled by `API_KEY_EXPIRY` (in minutes).

    ## Callback model

    A successful POST to `/proposals/` registers six callback URLs (any
    subset is allowed; missing channels are silently skipped). IMIS will POST
    JSON to each URL when the underlying state transition occurs. Delivery is
    fire-and-forget with a configurable timeout (`MARINE_CALLBACK_TIMEOUT_SECONDS`,
    default 5s). Every attempt is recorded in `MarineApiCallbackLog`.

    Each callback body is wrapped as:

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

    Partners should 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 (no automatic
    backoff, since Celery is disabled in this deployment).

  contact:
    name: NIC IMIS Engineering
    email: customercare@nicinsurance.co.tz

servers:
  - url: "{scheme}://{host}/{environment}/mware/marine/v1"
    description: NIC IMIS marine API
    variables:
      scheme:
        enum: [https, http]
        default: https
      host:
        default: imis.nicinsurance.co.tz
      environment:
        enum: [uat, production]
        default: uat

security:
  - apiKey: []
    apiSecret: []

tags:
  - name: Discovery
    description: Read-only endpoints for browsing the marine catalogue.
  - name: Proposals
    description: Submit a proposal and poll its lifecycle.
  - name: Callbacks
    description: Outbound webhook contracts (NIC → partner).

paths:
  /products/:
    get:
      tags: [Discovery]
      summary: List marine products
      description: |
        Returns every approved, non-rider product whose line of business is
        Marine. Use the `id` of an item to request its dynamic form.
      operationId: listMarineProducts
      responses:
        "200":
          description: Catalogue of marine products
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductListResponse'
              example:
                status: true
                count: 8
                results:
                  - id: 111
                    code: GIT01
                    name: Goods In Transit
                    description: This policy provides coverage to in land transit goods.
                    tira_code: null
                    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/111/form/
        "401":
          $ref: '#/components/responses/Unauthorized'

  /products/{product_id}/form/:
    get:
      tags: [Discovery]
      summary: Get the dynamic form schema for a marine product
      description: |
        Returns the form sections and fields 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.
      operationId: getMarineProductForm
      parameters:
        - $ref: '#/components/parameters/ProductIdPath'
      responses:
        "200":
          description: Dynamic form schema
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductFormResponse'
        "400":
          description: Product exists but is not under the Marine LOB
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "404":
          description: Product not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /proposals/:
    post:
      tags: [Proposals]
      summary: Submit a marine proposal
      description: |
        Creates an Orderpackage, Order, and OrderItem in IMIS; persists each
        key in `answers` through the standard dynamic-form pipeline; runs
        premium calculation; and registers callback URLs for the six
        downstream channels. The proposal then enters the same approval
        workflow used by the marketplace UI.

        `partner_reference` is the partner's idempotency key. Pair every
        callback body back to it.

        **Re-submission is blocked.** A `partner_reference` identifies one
        proposal for the life of the integration. Re-POSTing the same
        reference — whether the prior submission is still pending approval,
        has a control number, is already paid, or has commenced — returns
        HTTP 409 (`MARINE_DUPLICATE_PARTNER_REFERENCE`) with no side
        effects. To read state, call
        `GET /marine/v1/proposals/{partner_reference}/` instead.
      operationId: submitMarineProposal
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProposalRequest'
            example:
              partner_reference: PARTNER-REF-001
              product_id: 111
              commencement_date: "2026-06-01"
              end_date: "2027-05-31"
              sum_insured: 50000000
              property_name: "BL-2026-001 — Container TGHU1234567"
              underwrite_channel: 5
              customer:
                first_name: Jamhuri
                middle_name: Ya
                last_name: Muungano
                email: ops@partner.example
                phone: "+255700000000"
                identity_number: "19900101-12345-12345-12"
                gender: M
                date_of_birth: "01-01-1990"
              callbacks:
                control_number: https://partner.example/webhooks/cn
                approval_status: https://partner.example/webhooks/approval
                approval_decision: https://partner.example/webhooks/decision
                payment: https://partner.example/webhooks/pay
                tira_status: https://partner.example/webhooks/tira
                commencement: https://partner.example/webhooks/commence
              answers:
                "nature_of_business/occupation": FREIGHT FORWARDER
                "description(s)_of_good(s)_to_be_dispatched": DRY GOODS — 1×20FT CONTAINER
                "port_of_departure": DAR ES SALAAM
                "currency_type": TZS
      responses:
        "201":
          description: Proposal accepted; entering approval flow
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProposalAccepted'
        "400":
          description: |
            Validation error. The `code` field tells you which kind of
            problem occurred:

            * `MARINE_MISSING_FIELD` — a top-level field (partner_reference,
              product_id, customer.*, commencement_date) is missing.
            * `MARINE_INVALID_PHONE` — `customer.phone` is not a valid mobile
              phone number.
            * `MARINE_INVALID_IDENTITY_NUMBER` — `customer.identity_number`
              does not match any accepted identity format (see the
              `identity_number` schema). The `message` lists every accepted
              format and the `errors[0]` entry carries the rejected value and
              `accepted_formats`.
            * `MARINE_PRODUCT_NOT_MARINE` — the product exists but isn't
              under the Marine line of business.
            * `MARINE_PRODUCT_NOT_APPROVED` — product not in Approved status.
            * `MARINE_BAD_DATE` / `MARINE_BAD_DATE_RANGE` — date problems.
            * `MARINE_VALIDATION_FAILED` — one or more required dynamic-form
              fields are missing from `answers`. The `errors` array lists
              each missing field so you can fix them all in one go.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missing_required_answers:
                  summary: Two required answers missing
                  value:
                    status: false
                    code: MARINE_VALIDATION_FAILED
                    message: "2 required field(s) missing from the answers payload — see `errors` for the full list."
                    errors:
                      - section: DESCRIPTION OF GOODS
                        field_id: 14715
                        api_tag: currency_type
                        label: Currency type
                        type: currency
                        reason: missing
                      - section: DESCRIPTION OF GOODS
                        field_id: 14632
                        api_tag: port_of_departure
                        label: Port of departure
                        type: text
                        reason: missing
                invalid_identity_number:
                  summary: Customer identity number fails the format check
                  value:
                    status: false
                    code: MARINE_INVALID_IDENTITY_NUMBER
                    message: "Field 'customer.identity_number' value 'ab12' is not a valid customer identity number. Accepted formats: National ID (NIDA), e.g. 19900101-12345-12345-12; 20-digit number, e.g. 12345678901234567890; TIN with dashes, e.g. T-1234-1234-123-1; TIN, 12 digits after T, e.g. T123456789012; Passport / alphanumeric, 5-12 uppercase letters or digits, e.g. AB123456; Voter (Z) with dash, e.g. Z-123456789; Voter (Z), 9-12 digits, e.g. Z123456789; Numeric identifier, 4-12 digits, e.g. 0123456789; 3-3-3 grouped digits, e.g. 123-456-789"
                    errors:
                      - field: customer.identity_number
                        code: invalid_identity_number
                        value: ab12
                        accepted_formats:
                          - "National ID (NIDA), e.g. 19900101-12345-12345-12"
                          - "20-digit number, e.g. 12345678901234567890"
                          - "TIN with dashes, e.g. T-1234-1234-123-1"
                          - "TIN, 12 digits after T, e.g. T123456789012"
                          - "Passport / alphanumeric, 5-12 uppercase letters or digits, e.g. AB123456"
                          - "Voter (Z) with dash, e.g. Z-123456789"
                          - "Voter (Z), 9-12 digits, e.g. Z123456789"
                          - "Numeric identifier, 4-12 digits, e.g. 0123456789"
                          - "3-3-3 grouped digits, e.g. 123-456-789"
        "401":
          $ref: '#/components/responses/Unauthorized'
        "404":
          description: Referenced product not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "409":
          description: |
            Duplicate `partner_reference`. A submission with this reference
            already exists for the calling API key; re-submission is not
            allowed in any state. Poll
            `GET /marine/v1/proposals/{partner_reference}/` for the current
            status. The `errors[0]` entry includes the prior `submission_id`
            and `submit_status` so the partner can correlate without an
            extra round trip.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                duplicate_partner_reference:
                  summary: Re-submission of an existing reference
                  value:
                    status: false
                    code: MARINE_DUPLICATE_PARTNER_REFERENCE
                    message: "partner_reference 'PARTNER-REF-001' has already been submitted (submission_id=42, status=PENDING_APPROVAL). Re-submission is not allowed; poll the status endpoint instead."
                    errors:
                      - field: partner_reference
                        code: duplicate
                        submission_id: 42
                        submit_status: PENDING_APPROVAL
        "500":
          description: Internal error during submission pipeline
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /proposals/{partner_reference}/:
    get:
      tags: [Proposals]
      summary: Poll the current state of a submission
      description: |
        Returns the latest snapshot for the most-recent submission with this
        `partner_reference`. Useful as a fallback when callbacks are missed.
      operationId: getMarineProposalStatus
      parameters:
        - in: path
          name: partner_reference
          required: true
          schema: { type: string }
          description: The idempotency key sent on creation.
      responses:
        "200":
          description: Current submission state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProposalStatus'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "403":
          description: Submission belongs to a different partner
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "404":
          description: No submission with that partner_reference
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # The six callback channels are documented as paths under x-callbacks so
  # tools that don't understand OpenAPI 3.0's `webhooks` keyword still surface
  # them. The real URLs are partner-supplied — these path strings are
  # illustrative only.

  "x-callback/control_number":
    post:
      tags: [Callbacks]
      summary: "Webhook (i): control number issued"
      description: |
        Fired when GePG returns a control number for the package bill.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ControlNumberCallback'
      responses:
        "200":
          description: Acknowledged

  "x-callback/approval_status":
    post:
      tags: [Callbacks]
      summary: "Webhook (ii): approval workflow stage change"
      description: |
        Fired on every intermediate stage transition in the underwriting
        approval workflow.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApprovalStatusCallback'
      responses:
        "200":
          description: Acknowledged

  "x-callback/approval_decision":
    post:
      tags: [Callbacks]
      summary: "Webhook (iii): final approval decision"
      description: Fired when the workflow reaches its last node (approved / rejected).
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApprovalDecisionCallback'
      responses:
        "200":
          description: Acknowledged

  "x-callback/payment":
    post:
      tags: [Callbacks]
      summary: "Webhook (iv): payment received"
      description: Fired when GePG reports payment received against the control number.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PaymentCallback'
      responses:
        "200":
          description: Acknowledged

  "x-callback/tira_status":
    post:
      tags: [Callbacks]
      summary: "Webhook (v): TIRA submission status + errors"
      description: |
        Fired after TIRAMIS responds to the policy submission. Carries the
        cover-note / sticker number on success, or an `errors` array on
        failure.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TiraStatusCallback'
      responses:
        "200":
          description: Acknowledged

  "x-callback/commencement":
    post:
      tags: [Callbacks]
      summary: "Webhook (vi): policy commenced + documents"
      description: |
        Fired when policy items are commenced. Body includes the policy
        number, dates, and signed URLs for the policy and cover note PDFs.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CommencementCallback'
      responses:
        "200":
          description: Acknowledged

components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: API-KEY
      description: Bcrypt-hashed key issued by NIC IMIS operations.
    apiSecret:
      type: apiKey
      in: header
      name: API-SECRETE
      description: Matching secret. The header name is spelled "API-SECRETE".

  parameters:
    ProductIdPath:
      in: path
      name: product_id
      required: true
      schema: { type: integer }
      description: Product id from `/products/`.

  responses:
    Unauthorized:
      description: Missing, invalid, or expired credentials
      content:
        application/json:
          schema:
            type: object
            properties:
              detail: { type: string }
            example:
              detail: API-KEY and API-SECRETE headers are required

  schemas:
    Error:
      type: object
      properties:
        status: { type: boolean, example: false }
        code:
          type: string
          description: Machine-readable error code (e.g. MARINE_PRODUCT_NOT_FOUND).
        message: { type: string }
        errors:
          type: array
          description: |
            Field-level error details. Populated for `MARINE_VALIDATION_FAILED`
            and any other code that needs to report multiple issues at once.
          items: { $ref: '#/components/schemas/FieldError' }
      required: [status, code, message]

    FieldError:
      type: object
      description: |
        A single missing-required-field problem. Returned in the `errors`
        array of a `MARINE_VALIDATION_FAILED` response. Partners can fix
        every issue in one round trip — the validator emits all problems
        in a single response rather than failing on the first one.
      properties:
        section:
          type: string
          description: Form section name (matches the `name` from `/products/{id}/form/`).
        field_id: { type: integer, description: CheckListQuestionForm.id }
        api_tag:
          type: string
          description: JSON key the partner should add to `answers`.
        label: { type: string, description: Human label of the field. }
        type:
          type: string
          description: Field type (e.g. text, date, currency, radio, select).
        reason:
          type: string
          enum: [missing]
          description: Why this field was flagged. Currently always `missing`.

    LineBusinessBrief:
      type: object
      properties:
        id: { type: integer }
        code: { type: string }
        name: { type: string }

    ProductClassBrief:
      type: object
      properties:
        id: { type: integer }
        code: { type: string }
        name: { type: string }

    Product:
      type: object
      properties:
        id: { type: integer }
        code: { type: string }
        name: { type: string }
        description: { type: string, nullable: true }
        tira_code: { type: string, nullable: true }
        policy_term: { type: integer, nullable: true }
        policy_term_unit: { type: string, enum: [days, months, years], nullable: true }
        vat_apply: { type: boolean }
        line_business: { $ref: '#/components/schemas/LineBusinessBrief' }
        product_class: { $ref: '#/components/schemas/ProductClassBrief' }
        form_url: { type: string, format: uri }

    ProductListResponse:
      type: object
      properties:
        status: { type: boolean }
        count: { type: integer }
        results:
          type: array
          items: { $ref: '#/components/schemas/Product' }

    FormFieldOption:
      type: object
      properties:
        value: { type: string }
        label: { type: string }
        triggers_fields:
          type: array
          nullable: true
          items: { $ref: '#/components/schemas/FormField' }

    FormFieldValidation:
      type: object
      properties:
        min: { type: number, nullable: true }
        max: { type: number, nullable: true }
        min_length: { type: integer, nullable: true }
        max_length: { type: integer, nullable: true }
        validation_label: { type: string, nullable: true }

    FormField:
      type: object
      description: |
        A single input the partner must populate. The `api_tag` is the JSON
        key to use in the proposal's `answers` block.
      properties:
        id: { type: integer }
        api_tag:
          type: string
          description: JSON key for this field in the `answers` block.
        label: { type: string }
        description: { type: string, nullable: true }
        type:
          type: string
          enum:
            - integer
            - text
            - radio
            - bool
            - checkbox
            - textarea
            - select
            - datetime
            - future_datetime
            - past_datetime
            - date
            - future_date
            - past_date
            - time
            - file
            - email
            - corporate
            - decimal
            - days_of_year
            - currency
            - bank
            - money
            - intermediary
            - bankassurance
            - sales
            - property_name
            - branch
            - employer
            - direct_sale_force
        required: { type: boolean }
        validation: { $ref: '#/components/schemas/FormFieldValidation' }
        options:
          type: array
          nullable: true
          items: { $ref: '#/components/schemas/FormFieldOption' }

    FormSection:
      type: object
      properties:
        id: { type: integer }
        code: { type: string, nullable: true }
        name: { type: string, nullable: true }
        description: { type: string, nullable: true }
        index: { type: integer }
        fields:
          type: array
          items: { $ref: '#/components/schemas/FormField' }

    ProductFormResponse:
      type: object
      properties:
        status: { type: boolean }
        product:
          type: object
          properties:
            id: { type: integer }
            code: { type: string }
            name: { type: string }
            description: { type: string, nullable: true }
            tira_code: { type: string, nullable: true }
        submit_url: { type: string, format: uri }
        sections:
          type: array
          items: { $ref: '#/components/schemas/FormSection' }

    CustomerIndividual:
      type: object
      required: [first_name, last_name, phone, identity_number]
      properties:
        type:
          type: string
          enum: [individual, corporate]
          default: individual
          description: |
            Customer type. Omit (or `individual`) for a person and supply the
            individual fields below. Set `corporate` to insure an organisation
            and supply the corporate fields (`name`, `reg_no`, …) instead — see
            `CustomerCorporate`. The internal `corporate_id` is no longer
            accepted; describe the corporate the same way as an individual.
        first_name: { type: string }
        middle_name: { type: string, nullable: true }
        last_name: { type: string }
        email: { type: string, format: email, nullable: true }
        phone: { type: string, description: "E.164 format preferred." }
        identity_number:
          type: string
          description: |
            Customer identity / policy-holder ID number. Validated against a
            fixed set of accepted formats; a value that matches none of them
            is rejected with `MARINE_INVALID_IDENTITY_NUMBER` (HTTP 400) and
            the full list of accepted formats in the `message`. Accepted:

            * National ID (NIDA), e.g. `19900101-12345-12345-12`
            * 20-digit number, e.g. `12345678901234567890`
            * TIN with dashes, e.g. `T-1234-1234-123-1`
            * TIN, 12 digits after `T`, e.g. `T123456789012`
            * Passport / alphanumeric, 5–12 uppercase letters or digits, e.g. `AB123456`
            * Voter (Z) with dash, e.g. `Z-123456789`
            * Voter (Z), 9–12 digits, e.g. `Z123456789`
            * Numeric identifier, 4–12 digits, e.g. `0123456789`
            * 3-3-3 grouped digits, e.g. `123-456-789`

            The value is trimmed before matching. Uppercase is required for
            alphanumeric (passport) IDs.
          pattern: '^([12][0-9]{7}-[0-9]{5}-[0-9]{5}-[0-9]{2}|[0-9]{20}|T-[0-9]{4}-[0-9]{4}-[0-9]{3}-[0-9]{1}|T[0-9]{12}|[A-Z0-9]{5,12}|Z-[0-9]{9,10}|Z[0-9]{9,10}|[0-9]{10}|[0-9]{3}-[0-9]{3}-[0-9]{3}|[0-9]{9}|[0-9]{9,12}|[0-9]{4,12}|Z[0-9]{9,12})$'
          example: "19900101-12345-12345-12"
        gender: { type: string, enum: [M, F], nullable: true }
        date_of_birth: { type: string, description: "DD-MM-YYYY or YYYY-MM-DD" }
        signature: { type: string, nullable: true }

    CustomerCorporate:
      type: object
      description: |
        Corporate customer. Send inside the `customer` object with
        `type: corporate`. The organisation is matched by `reg_no` (then `tin`)
        and reused if it already exists, so repeat submissions do not create
        duplicate corporates. No internal id is required.
      required: [type, name, reg_no]
      properties:
        type: { type: string, enum: [corporate] }
        name: { type: string, description: "Registered company name." }
        reg_no: { type: string, description: "Business registration number (primary match key)." }
        tin: { type: string, nullable: true, description: "Taxpayer identification number (secondary match key)." }
        vrn: { type: string, nullable: true, description: "VAT registration number." }
        short_name: { type: string, nullable: true }
        nature_business: { type: string, nullable: true }
        phone: { type: string, nullable: true, description: "Contact mobile; validated when present." }
        email: { type: string, format: email, nullable: true }
        address: { type: string, nullable: true, description: "Postal address." }

    CallbackURLs:
      type: object
      description: |
        Any subset is allowed. Channels with no URL are silently skipped.
      properties:
        control_number: { type: string, format: uri }
        approval_status: { type: string, format: uri }
        approval_decision: { type: string, format: uri }
        payment: { type: string, format: uri }
        tira_status: { type: string, format: uri }
        commencement: { type: string, format: uri }

    ProposalRequest:
      type: object
      required: [partner_reference, product_id, commencement_date, customer]
      properties:
        partner_reference:
          type: string
          maxLength: 120
          description: |
            Idempotency key chosen by the partner. Must be unique per API
            key — re-using a value that has already been accepted returns
            HTTP 409 (`MARINE_DUPLICATE_PARTNER_REFERENCE`) regardless of
            the prior submission's state.
        product_id: { type: integer }
        commencement_date:
          type: string
          description: Cover start (YYYY-MM-DD, DD-MM-YYYY, or ISO-8601 datetime).
        end_date:
          type: string
          description: Cover end. Defaults to `commencement_date + 1 year − 1 day`.
        sum_insured:
          type: number
          description: Sum insured for the OrderItem.
        sum_assured:
          type: number
          description: Alias for `sum_insured`.
        property_name:
          type: string
          description: |
            Short description of the insured item (cargo BL number, hull name,
            container number, etc.). Stored on `OrderItem.policy_property_name`.
            Defaults to `partner_reference` if omitted.
        underwrite_channel:
          type: integer
          default: 5
          description: |
            UNDERWRITE_CHANNEL enum value. 5 = external partner API.
        customer: { $ref: '#/components/schemas/CustomerIndividual' }
        callbacks: { $ref: '#/components/schemas/CallbackURLs' }
        answers:
          type: object
          additionalProperties: { type: string }
          description: |
            Map of `api_tag` → answer string, with one entry per visible field
            in the product's form schema (`/products/{id}/form/`).

    ProposalAccepted:
      type: object
      properties:
        status: { type: boolean, example: true }
        code: { type: string, example: MARINE_PROPOSAL_RECEIVED }
        message: { type: string }
        submission_id: { type: integer }
        partner_reference: { type: string }
        submit_status: { type: string, enum: [RECEIVED, PENDING_APPROVAL] }
        package_id: { type: integer }
        package_number: { type: string }
        order_item_id: { type: integer }
        premium: { type: number }
        premium_vat_amount: { type: number }
        premium_vat_exclusive: { type: number }
        sum_insured: { type: number }

    ProposalStatus:
      type: object
      properties:
        status: { type: boolean }
        id: { type: integer }
        partner_reference: { type: string }
        submit_status:
          type: string
          enum:
            - RECEIVED
            - PENDING_APPROVAL
            - APPROVED
            - REJECTED
            - BILLED
            - PAID
            - TIRA_SUBMITTED
            - TIRA_FAILED
            - COMMENCED
            - FAILED
        cn_ack: { type: boolean }
        approval_status_ack: { type: boolean }
        approval_decision_ack: { type: boolean }
        pay_ack: { type: boolean }
        tira_ack: { type: boolean }
        commencement_ack: { type: boolean }
        last_error: { type: string, nullable: true }
        control_number: { type: string, nullable: true }
        premium: { type: number, nullable: true }
        policy_number: { type: string, nullable: true }

    CallbackEnvelope:
      type: object
      description: Common envelope for every callback body.
      properties:
        submission_id: { type: integer }
        partner_reference: { type: string }
        kind:
          type: string
          enum:
            - CONTROL_NUMBER
            - APPROVAL_STATUS
            - APPROVAL_DECISION
            - PAYMENT
            - TIRA_STATUS
            - COMMENCEMENT

    ControlNumberCallback:
      allOf:
        - $ref: '#/components/schemas/CallbackEnvelope'
        - type: object
          properties:
            control_number: { type: string }
            tax_invoice_document_url: { type: string, format: uri }

    ApprovalStatusCallback:
      allOf:
        - $ref: '#/components/schemas/CallbackEnvelope'
        - type: object
          properties:
            stage: { type: string, nullable: true, description: "Stage name." }
            status: { type: string }
            comment: { type: string, nullable: true }

    ApprovalDecisionCallback:
      allOf:
        - $ref: '#/components/schemas/CallbackEnvelope'
        - type: object
          properties:
            decision: { type: string, enum: [APPROVED, REJECTED] }
            comment: { type: string, nullable: true }

    PaymentCallback:
      allOf:
        - $ref: '#/components/schemas/CallbackEnvelope'
        - type: object
          properties:
            package_id: { type: integer }
            amount: { type: number }
            receipt_number: { type: string, nullable: true }
            control_number: { type: string }
            receipt_document_url: { type: string, format: uri, nullable: true }

    TiraStatusCallback:
      allOf:
        - $ref: '#/components/schemas/CallbackEnvelope'
        - type: object
          properties:
            status: { type: string, enum: [SUBMITTED, FAILED] }
            cover_note: { type: string, nullable: true }
            sticker_number: { type: string, nullable: true }
            errors:
              type: array
              items: { type: string }

    CommencementCallback:
      allOf:
        - $ref: '#/components/schemas/CallbackEnvelope'
        - type: object
          properties:
            package_id: { type: integer }
            policy_number: { type: string }
            policy_start_date: { type: string }
            policy_end_date: { type: string }
            policy_document_url: { type: string, format: uri, nullable: true }
            covernote_document_url: { type: string, format: uri, nullable: true }
