openapi: 3.1.0
info:
  title: Insurex24 API
  version: 1.0.0
  summary: One API for policies, claims, KYC, OCR, communication and renewals.
  description: |
    The Insurex24 API is a REST API over HTTPS. It accepts and returns JSON,
    uses standard HTTP status codes, and authenticates with a Bearer API key.

    ## 5-minute quickstart
    1. **Get a sandbox key** — keys start with `sk_test_`. Sandbox calls never touch real insurers, UIDAI or payment rails.
    2. **Verify a customer** — `POST /kyc/verify`
    3. **Get a quote** — `POST /quotes`
    4. **Issue the policy** — `POST /policies/issue` with the `quote_id`
    5. **Listen for events** — subscribe to webhooks such as `policy.issued` and `claim.status_changed`

    ```bash
    curl https://sandbox.insurex24.com/v1/kyc/verify \
      -H "Authorization: Bearer sk_test_demo" \
      -H "Content-Type: application/json" \
      -d '{"aadhaar_number":"XXXX-XXXX-1234","name":"Priya Mehta","dob":"1988-03-22","consent":true,"purpose":"insurance_onboarding"}'
    ```

    ## Conventions
    - **IDs** are prefixed by type: `kyc_`, `qt_`, `POL-`, `CLM-`, `doc_`, `ntf_`, `rnw_`.
    - **Amounts** are integers in the smallest sensible unit of `currency` (INR rupees, no paise) unless stated.
    - **Dates** are `YYYY-MM-DD`; timestamps are ISO 8601 UTC.
    - **Idempotency** — send an `Idempotency-Key` header on every `POST`. Retrying with the same key returns the original result instead of creating a duplicate.
    - **Pagination** — list endpoints are cursor-based: pass `starting_after` with the last ID you received.
    - **Errors** — every error has the same shape: `{ "error": { "type", "code", "message", "param", "request_id" } }`.
    - **Rate limits** — 100 req/s per key in production, 25 req/s in sandbox. Check the `X-RateLimit-Remaining` header; a `429` includes `Retry-After`.

    ## Sandbox test values
    | Input | Result |
    |---|---|
    | Aadhaar ending `1234` | KYC `verified` |
    | Aadhaar ending `0000` | KYC `failed` (`identity_mismatch`) |
    | Claim `estimated_loss` > 500000 | Claim routed to `under_review` with high fraud score |
    | Mobile `+919999999999` | SMS returns `undelivered` |
  license:
    name: Proprietary
    url: https://insurex24.com
  contact:
    name: Insurex24 Developer Support
    email: admin@insurex24.com
    url: https://insurex24.com/docs.html
servers:
  - url: https://sandbox.insurex24.com/v1
    description: Sandbox (test keys, sk_test_...)
  - url: https://api.insurex24.com/v1
    description: Production (live keys, sk_live_...)
  - url: http://127.0.0.1:4010
    description: Local mock (npx @stoplight/prism-cli mock openapi.yaml)
security:
  - bearerAuth: []
tags:
  - name: KYC
    description: Identity verification (Aadhaar eKYC, PAN, name/DOB match).
  - name: Quotes
    description: Price a product before issuing a policy.
  - name: Policies
    description: Issue and retrieve policies.
  - name: Claims
    description: First notice of loss, document intake and claim status.
  - name: Renewals
    description: Batch renewal notices, premium recalculation and payment links.
  - name: OCR
    description: Extract structured data from PDFs and images.
  - name: Notifications
    description: Transactional SMS and email.

paths:
  /kyc/verify:
    post:
      tags: [KYC]
      operationId: verifyKyc
      summary: Verify a customer's identity
      description: Runs Aadhaar eKYC plus a fuzzy name and DOB match. Explicit customer consent is required.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/KycRequest' }
            example:
              aadhaar_number: XXXX-XXXX-1234
              name: Priya Mehta
              dob: '1988-03-22'
              consent: true
              purpose: insurance_onboarding
      responses:
        '200':
          description: Verification completed (check `status`).
          headers:
            X-RateLimit-Remaining: { $ref: '#/components/headers/RateLimitRemaining' }
          content:
            application/json:
              schema: { $ref: '#/components/schemas/KycResult' }
              example:
                id: kyc_8FJ2k91LmQ
                status: verified
                match_score: 97.8
                checks: { aadhaar_otp: passed, name_match: passed, dob_match: passed }
                compliance: { irdai_logged: true, consent_recorded_at: '2026-09-30T11:05:00Z' }
                created_at: '2026-09-30T11:05:02Z'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '422': { $ref: '#/components/responses/Unprocessable' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /quotes:
    post:
      tags: [Quotes]
      operationId: createQuote
      summary: Create a quote
      description: Prices a product for an insured person and asset. Quotes are valid for 7 days.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/QuoteRequest' }
            example:
              product: motor_comprehensive
              kyc_id: kyc_8FJ2k91LmQ
              vehicle: { registration: MH12AB1234, year: 2022, idv: 650000 }
              add_ons: [zero_depreciation, roadside_assistance]
      responses:
        '201':
          description: Quote created.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Quote' }
              example:
                id: qt_3kPz81xNq
                product: motor_comprehensive
                premium: { base: 16200, add_ons: 2300, tax: 3330, total: 21830, currency: INR }
                valid_until: '2026-10-07'
                created_at: '2026-09-30T09:13:40Z'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /policies/issue:
    post:
      tags: [Policies]
      operationId: issuePolicy
      summary: Issue a policy
      description: |
        Converts a quote into a live policy. Insurex24 validates the payload, maps it to your
        core policy admin system (ACORD), and triggers SMS and email confirmations.
        Fires the `policy.issued` webhook.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PolicyIssueRequest' }
            example:
              quote_id: qt_3kPz81xNq
              insured: { name: Rahul Sharma, dob: '1990-05-14', mobile: '+919812345678', email: rahul@example.com }
              start_date: '2026-10-01'
              payment_reference: pay_Nx81kQ2
      responses:
        '201':
          description: Policy issued.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Policy' }
              example:
                id: POL-2026-8847291
                status: active
                product: motor_comprehensive
                insured: { name: Rahul Sharma, dob: '1990-05-14', mobile: '+919812345678', email: rahul@example.com }
                premium: { total: 21830, currency: INR }
                start_date: '2026-10-01'
                end_date: '2027-09-30'
                documents: { policy_pdf: 'https://sandbox.insurex24.com/v1/files/POL-2026-8847291.pdf' }
                created_at: '2026-09-30T09:14:00Z'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /policies:
    get:
      tags: [Policies]
      operationId: listPolicies
      summary: List policies
      parameters:
        - { name: status, in: query, schema: { type: string, enum: [active, lapsed, cancelled, expired] } }
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/StartingAfter'
      responses:
        '200':
          description: A page of policies.
          content:
            application/json:
              schema:
                type: object
                required: [data, has_more]
                properties:
                  data: { type: array, items: { $ref: '#/components/schemas/Policy' } }
                  has_more: { type: boolean }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /policies/{policy_id}:
    get:
      tags: [Policies]
      operationId: getPolicy
      summary: Retrieve a policy
      parameters:
        - $ref: '#/components/parameters/PolicyId'
      responses:
        '200':
          description: The policy.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Policy' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }

  /claims/submit:
    post:
      tags: [Claims]
      operationId: submitClaim
      summary: Submit a claim (first notice of loss)
      description: |
        Registers a claim against an active policy. Attached documents are OCR-processed and the claim is
        fraud-scored automatically. Fires `claim.status_changed` whenever the status moves.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ClaimRequest' }
            example:
              policy_id: POL-2026-8847291
              incident: { type: accident, date: '2026-09-15', location: 'Pune, Maharashtra', description: Rear-ended at a signal }
              estimated_loss: 45000
              document_ids: [doc_claimform_91, doc_photos_22]
      responses:
        '201':
          description: Claim registered.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Claim' }
              example:
                id: CLM-2026-5544
                policy_id: POL-2026-8847291
                status: registered
                estimated_loss: 45000
                fraud: { score: 12, risk: low }
                created_at: '2026-09-30T10:22:02Z'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /claims/{claim_id}:
    get:
      tags: [Claims]
      operationId: getClaim
      summary: Retrieve a claim
      parameters:
        - { name: claim_id, in: path, required: true, schema: { type: string }, example: CLM-2026-5544 }
      responses:
        '200':
          description: The claim.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Claim' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }

  /renewals/trigger:
    post:
      tags: [Renewals]
      operationId: triggerRenewals
      summary: Trigger renewal notices
      description: Recalculates renewal premiums, applies eligible discounts, generates payment links and notifies policyholders. Runs asynchronously; the response is a job you can poll or receive via the `renewal.completed` webhook.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/RenewalRequest' }
            example:
              policy_ids: [POL-2026-8847291, POL-2026-9912]
              renewal_date: '2026-10-31'
              channels: [sms, email]
              apply_loyalty_discount: true
      responses:
        '202':
          description: Renewal job accepted.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/RenewalJob' }
              example:
                id: rnw_71bQ0aa
                status: processing
                policy_count: 2
                created_at: '2026-09-30T00:00:00Z'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /ocr/extract:
    post:
      tags: [OCR]
      operationId: extractDocument
      summary: Extract data from a document
      description: Upload a PDF or image (max 10 MB) and receive structured fields with per-field confidence.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file, document_type]
              properties:
                file: { type: string, format: binary }
                document_type: { type: string, enum: [claim_form, rc_book, driving_licence, pan_card, invoice, generic] }
      responses:
        '200':
          description: Extraction result.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OcrResult' }
              example:
                id: doc_claimform_91
                document_type: claim_form
                confidence: 99.2
                fields:
                  policy_number: { value: POL-2026-8847291, confidence: 99.8 }
                  incident_date: { value: '2026-09-15', confidence: 99.1 }
                  claimed_amount: { value: '45000', confidence: 98.7 }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '413':
          description: File larger than 10 MB.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /notifications/send:
    post:
      tags: [Notifications]
      operationId: sendNotification
      summary: Send an SMS or email
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/NotificationRequest' }
            example:
              channel: sms
              to: '+919812345678'
              template: policy_issued
              variables: { name: Rahul, policy_id: POL-2026-8847291 }
      responses:
        '202':
          description: Queued for delivery.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Notification' }
              example:
                id: ntf_5c2Lx0
                channel: sms
                status: queued
                created_at: '2026-09-30T09:14:01Z'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }

webhooks:
  policy.issued:
    post:
      operationId: onPolicyIssued
      summary: A policy was issued
      description: Delivered with an `X-Insurex24-Signature` header (HMAC-SHA256 of the raw body using your webhook secret). Respond `2xx` within 10 s; failures retry with exponential backoff for 24 h.
      requestBody:
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/Event'
                - type: object
                  properties: { data: { $ref: '#/components/schemas/Policy' } }
      responses:
        '200': { description: Acknowledged. }
  claim.status_changed:
    post:
      operationId: onClaimStatusChanged
      summary: A claim changed status
      requestBody:
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/Event'
                - type: object
                  properties: { data: { $ref: '#/components/schemas/Claim' } }
      responses:
        '200': { description: Acknowledged. }
  renewal.completed:
    post:
      operationId: onRenewalCompleted
      summary: A renewal job finished
      requestBody:
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/Event'
                - type: object
                  properties: { data: { $ref: '#/components/schemas/RenewalJob' } }
      responses:
        '200': { description: Acknowledged. }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: 'Send `Authorization: Bearer <key>`. Use `sk_test_...` keys in sandbox and `sk_live_...` in production.'

  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: Unique value (e.g. a UUID) that makes a POST safe to retry. Kept for 24 hours.
      schema: { type: string, maxLength: 255 }
      example: 6f1c2a4e-0b5d-4b8e-9c61-2f0d7a9e3b11
    Limit:
      name: limit
      in: query
      schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
    StartingAfter:
      name: starting_after
      in: query
      description: Cursor — the ID of the last item on the previous page.
      schema: { type: string }
    PolicyId:
      name: policy_id
      in: path
      required: true
      schema: { type: string }
      example: POL-2026-8847291

  headers:
    RateLimitRemaining:
      description: Requests left in the current one-second window.
      schema: { type: integer }

  responses:
    BadRequest:
      description: The request is malformed or missing a required field.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example:
            error: { type: invalid_request_error, code: parameter_missing, message: 'Missing required field: consent', param: consent, request_id: req_9sd81Kx }
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example:
            error: { type: authentication_error, code: invalid_api_key, message: The API key provided is invalid., request_id: req_1aa02Lm }
    NotFound:
      description: The resource does not exist.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example:
            error: { type: invalid_request_error, code: resource_missing, message: 'No such policy: POL-0000', param: policy_id, request_id: req_7Qe2mZ1 }
    Conflict:
      description: The Idempotency-Key was reused with a different payload, or the quote was already converted.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Unprocessable:
      description: The request is valid JSON but fails a business rule.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
          example:
            error: { type: business_rule_error, code: quote_expired, message: Quote qt_3kPz81xNq expired on 2026-10-07., param: quote_id, request_id: req_0Zk3pP4 }
    RateLimited:
      description: Too many requests.
      headers:
        Retry-After: { description: Seconds to wait before retrying., schema: { type: integer } }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }

  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [type, code, message, request_id]
          properties:
            type: { type: string, enum: [invalid_request_error, authentication_error, business_rule_error, rate_limit_error, api_error] }
            code: { type: string, description: 'Machine-readable code, stable across versions.' }
            message: { type: string, description: Human-readable explanation. }
            param: { type: string, description: 'The request field that caused the error, if any.' }
            request_id: { type: string, description: Quote this when contacting support. }

    Money:
      type: object
      required: [total, currency]
      properties:
        base: { type: integer }
        add_ons: { type: integer }
        tax: { type: integer }
        total: { type: integer }
        currency: { type: string, example: INR }

    Person:
      type: object
      required: [name, dob]
      properties:
        name: { type: string }
        dob: { type: string, format: date }
        mobile: { type: string, description: E.164 format, example: '+919812345678' }
        email: { type: string, format: email }

    KycRequest:
      type: object
      required: [aadhaar_number, name, dob, consent, purpose]
      properties:
        aadhaar_number: { type: string, description: Masked or full Aadhaar number., example: XXXX-XXXX-1234 }
        pan: { type: string, pattern: '^[A-Z]{5}[0-9]{4}[A-Z]$' }
        name: { type: string }
        dob: { type: string, format: date }
        consent: { type: boolean, const: true, description: Must be true — explicit customer consent. }
        purpose: { type: string, enum: [insurance_onboarding, claim_settlement, policy_servicing] }

    KycResult:
      type: object
      required: [id, status, created_at]
      properties:
        id: { type: string }
        status: { type: string, enum: [verified, failed, pending_otp] }
        match_score: { type: number, minimum: 0, maximum: 100 }
        failure_code: { type: string, enum: [identity_mismatch, aadhaar_invalid, otp_expired] }
        checks:
          type: object
          additionalProperties: { type: string, enum: [passed, failed, skipped] }
        compliance:
          type: object
          properties:
            irdai_logged: { type: boolean }
            consent_recorded_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }

    QuoteRequest:
      type: object
      required: [product, kyc_id]
      properties:
        product: { type: string, enum: [motor_comprehensive, motor_third_party, health_individual, health_family, term_life] }
        kyc_id: { type: string }
        vehicle:
          type: object
          description: Required for motor products.
          properties:
            registration: { type: string }
            year: { type: integer }
            idv: { type: integer, description: Insured declared value. }
        sum_insured: { type: integer, description: Required for health and life products. }
        add_ons: { type: array, items: { type: string } }

    Quote:
      type: object
      required: [id, product, premium, valid_until]
      properties:
        id: { type: string }
        product: { type: string }
        premium: { $ref: '#/components/schemas/Money' }
        valid_until: { type: string, format: date }
        created_at: { type: string, format: date-time }

    PolicyIssueRequest:
      type: object
      required: [quote_id, insured, start_date]
      properties:
        quote_id: { type: string }
        insured: { $ref: '#/components/schemas/Person' }
        start_date: { type: string, format: date }
        payment_reference: { type: string, description: Reference from your payment gateway. }
        metadata: { type: object, additionalProperties: { type: string }, description: 'Your own key-value pairs, returned on every read.' }

    Policy:
      type: object
      required: [id, status, product, start_date, end_date]
      properties:
        id: { type: string }
        status: { type: string, enum: [active, lapsed, cancelled, expired] }
        product: { type: string }
        insured: { $ref: '#/components/schemas/Person' }
        premium: { $ref: '#/components/schemas/Money' }
        start_date: { type: string, format: date }
        end_date: { type: string, format: date }
        documents:
          type: object
          properties:
            policy_pdf: { type: string, format: uri }
        metadata: { type: object, additionalProperties: { type: string } }
        created_at: { type: string, format: date-time }

    ClaimRequest:
      type: object
      required: [policy_id, incident, estimated_loss]
      properties:
        policy_id: { type: string }
        incident:
          type: object
          required: [type, date]
          properties:
            type: { type: string, enum: [accident, theft, fire, natural_calamity, hospitalisation, death] }
            date: { type: string, format: date }
            location: { type: string }
            description: { type: string }
        estimated_loss: { type: integer, minimum: 1 }
        document_ids: { type: array, items: { type: string }, description: IDs returned by `POST /ocr/extract`. }

    Claim:
      type: object
      required: [id, policy_id, status]
      properties:
        id: { type: string }
        policy_id: { type: string }
        status: { type: string, enum: [registered, under_review, approved, rejected, settled] }
        estimated_loss: { type: integer }
        approved_amount: { type: integer }
        fraud:
          type: object
          properties:
            score: { type: integer, minimum: 0, maximum: 100 }
            risk: { type: string, enum: [low, medium, high] }
        created_at: { type: string, format: date-time }

    RenewalRequest:
      type: object
      required: [policy_ids, renewal_date]
      properties:
        policy_ids: { type: array, items: { type: string }, maxItems: 1000 }
        renewal_date: { type: string, format: date }
        channels: { type: array, items: { type: string, enum: [sms, email, whatsapp] }, default: [email] }
        apply_loyalty_discount: { type: boolean, default: false }

    RenewalJob:
      type: object
      required: [id, status]
      properties:
        id: { type: string }
        status: { type: string, enum: [processing, completed, partially_failed] }
        policy_count: { type: integer }
        notified: { type: integer }
        failed: { type: integer }
        created_at: { type: string, format: date-time }

    OcrResult:
      type: object
      required: [id, document_type, fields]
      properties:
        id: { type: string }
        document_type: { type: string }
        confidence: { type: number }
        fields:
          type: object
          additionalProperties:
            type: object
            properties:
              value: { type: string }
              confidence: { type: number }

    NotificationRequest:
      type: object
      required: [channel, to, template]
      properties:
        channel: { type: string, enum: [sms, email] }
        to: { type: string, description: 'E.164 mobile for SMS, address for email.' }
        template: { type: string, description: 'Template ID, e.g. policy_issued, claim_update, renewal_due.' }
        variables: { type: object, additionalProperties: { type: string } }

    Notification:
      type: object
      required: [id, channel, status]
      properties:
        id: { type: string }
        channel: { type: string }
        status: { type: string, enum: [queued, sent, delivered, undelivered] }
        created_at: { type: string, format: date-time }

    Event:
      type: object
      required: [id, type, created_at, data]
      properties:
        id: { type: string, example: evt_1Nq82kd }
        type: { type: string, example: policy.issued }
        created_at: { type: string, format: date-time }
        data: { type: object }
