openapi: 3.1.0
info:
  title: Surepass Esign API
  version: 2.0.0
  description: |
    API for the Surepass Esign platform: upload documents, start Esign
    sessions (Aadhaar OTP or Suresign), track signer status, and download
    the final signed PDF. Reusable templates let you save signer layouts
    once and initialize Esign sessions from them at runtime.
  contact:
    name: Surepass Technical Support
    email: techsupport@surepass.io
servers:
  - url: https://esign-api-v2.surepass.app
    description: Sandbox
  - url: https://prod-esign-api-v2.surepass.app
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Files
    description: Upload PDF documents and image assets used in Esign flows.
  - name: Esign
    x-displayName: Esign
    description: Initialize Esign sessions, track signer status, and download signed documents.
  - name: Templates
    description: Save reusable signer/element blueprints and initialize Esign sessions from them.
  - name: Stamps
    description: Procure e-stamps — look up supported states and articles, create orders, track status, and download stamp PDFs.

paths:
  /api/v1/files/upload-pdf:
    post:
      tags: [Files]
      operationId: uploadPdf
      summary: Upload PDF
      description: Upload a PDF file (max size 50MB) for Esign.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: The PDF file to upload (max 50MB).
      responses:
        '200':
          description: File uploaded successfully.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/UploadedFile'
              example:
                data:
                  user_id: user_xxx
                  client_id: file_xxx
                  name: Form_26.pdf
                  mime: application/pdf
                status_code: 200
                message: Success
                success: true
        '400':
          description: |
            Upload failed. Possible error codes:

            - `file_empty` — No file uploaded or file is empty.
            - `file_too_large` — Uploaded file exceeds maximum size (50MB).
            - `invalid_pdf` — File is not a valid PDF.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 400
                message: file_empty
                success: false
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/files/upload-asset:
    post:
      tags: [Files]
      operationId: uploadAsset
      summary: Upload asset
      description: |
        Upload an image asset (JPEG or PNG). Use the returned `client_id` as:

        - `face_ref_file_id` on a signer to enable face verification, or
        - the `value` of an `image` element placed on the document.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: JPEG or PNG image to upload.
      responses:
        '200':
          description: Asset uploaded successfully.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/UploadedFile'
              example:
                data:
                  user_id: user_xxx
                  client_id: file_faceReferenceImage
                  name: reference.png
                  mime: image/png
                status_code: 200
                message: Success
                success: true
        '400':
          description: Upload failed (empty file or unsupported image format).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 400
                message: Upload failed (empty file or unsupported image format).
                success: false
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/esign/initialize-esign:
    post:
      tags: [Esign]
      operationId: initializeEsign
      summary: Initialize Esign
      description: |
        Start an Esign session for one or more signers on an uploaded document.

        - `page` inside elements is zero-indexed: `0` is the first page.
        - Multiple elements can be added per signer on different pages.
        - `email_template_overrides` customizes invite email branding; when
          `company_name` is provided, default Surepass branding is hidden for
          white-label emails.
        - `stamp` optionally reserves e-stamps into the flow — either by search
          (`amount` + `state_code` + `article_id`, optionally narrowed by party
          names) or by explicit `stamp_instance_ids`. Reserved stamp PDFs are
          prefixed to the uploaded PDF before signing starts.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InitializeEsignRequest'
            example:
              sign_type: aadhaar
              auth_mode: 1
              file_id: file_onwEUoMxnWhewUzodZeW
              folder: lehddsun
              expiry_seconds: 7200
              email_template_overrides:
                company_name: Acme Corp
                legal_name: Acme Corporation Pvt Ltd.
                support_email: support@acme.com
              signers:
                - full_name: Test1
                  email: test1@example.com
                  reason: Test1 Sign
                  verify_email: false
                  verify_mobile: false
                  name_match: true
                  face_ref_file_id: file_faceReferenceImage
                  face_match_threshold: 80
                  face_max_attempts: 5
                  notification:
                    invite: true
                    completion: true
                    whatsapp: true
                  elements:
                    - x: 100
                      y: 10
                      page: 0
                      type: signature
                      allow_wet_signature: false
                - full_name: Test2
                  email: test2@example.com
                  mobile: '2222222222'
                  reason: Test2 Sign
                  verify_email: true
                  verify_mobile: false
                  notification:
                    invite: false
                    completion: true
                    whatsapp: false
                  elements:
                    - x: 100
                      y: 10
                      page: 0
                      type: signature
      responses:
        '200':
          description: Esign session created.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/EsignSession'
              example:
                data:
                  client_id: esign_lpHGXloMQwkNPPXvBHCH
                  status: pending
                  file_id: file_onwEUoMxnWhewUzodZeW
                  signers: []
                status_code: 200
                message: Success
                success: true
        '400':
          description: |
            Validation failed. Possible error codes:

            - `missing_fields` — Required fields are missing in the request.
            - `invalid_file_id` — The provided file ID is invalid or does not exist.
            - `invalid_sign_type` — Must be either `aadhaar` or `suresign`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 400
                message: missing_fields
                success: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: |
            Requested stamp inventory is not available. Possible error codes:

            - `stamp_combination_not_found` — no available stamps match the search parameters.
            - `inventory_conflict` — requested stamp instances are no longer available.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 422
                message: stamp_combination_not_found
                success: false

  /api/v1/esign/details:
    get:
      tags: [Esign]
      operationId: getEsignDetails
      summary: Get signer details
      description: Retrieve signer status and metadata for a specific Esign session.
      parameters:
        - name: client_id
          in: query
          required: true
          description: Esign session client ID (`esign_...`).
          schema:
            type: string
          example: esign_PsLIyRDIHAqyFWhZqhDT
      responses:
        '200':
          description: |
            Session details.

            - `signers[].aadhaar_cert_details` is present for Aadhaar signers
              after Aadhaar signing completes; `null` for non-Aadhaar signers
              and pending Aadhaar signers.
            - `signers[].name_match.status` is `pending` when waiting for
              Aadhaar certificate details, `completed` when the score is
              calculated, and `skipped` when not requested.
            - `signers[].face_match.enforce` is `true` when initialization
              supplied `face_ref_file_id`; `liveness_score` and `match_score`
              stay `null` until face verification succeeds.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/EsignDetails'
              example:
                data:
                  user_id: user_kmJytYATMawoorDzWrUQ
                  client_id: esign_PsLIyRDIHAqyFWhZqhDT
                  created_at: '2025-05-13T12:06:01.344000'
                  sign_type: aadhaar
                  file_id: file_ugSBmcbzETuPjUqQLyts
                  allow_download: true
                  expiry_seconds: 7200
                  status: completed
                  signer_idx: 1
                  signers:
                    - signer_id: signer_vxgTyyOxYDhkhsIKQtda
                      full_name: Test1
                      email: test1@example.com
                      status: completed
                      aadhaar_cert_details:
                        name: Test Signer
                        country: IN
                        state: Delhi
                        pin_code: '110001'
                        aadhaar: '0000'
                        year_of_birth: '1990'
                        gender: M
                      name_match:
                        enforce: true
                        prefill_name: Test1
                        signer_name: Test Signer
                        score: 31.25
                        status: completed
                      face_match:
                        enforce: true
                        ref_file_id: file_faceReferenceImage
                        live_file_id: file_faceLiveImage
                        match_threshold: 80
                        max_attempts: 5
                        liveness_score: 0.93
                        match_score: 87
                      positions:
                        '1':
                          - x: 100
                            y: 200
                status_code: 200
                message: Success
                success: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Esign session not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 404
                message: Esign client with client_id 'esign_xxx' not found.
                success: false

  /api/v1/esign/download-pdf:
    get:
      tags: [Esign]
      operationId: downloadSignedPdf
      summary: Download signed PDF
      description: Download the final signed PDF after the Esign session completes.
      parameters:
        - name: client_id
          in: query
          required: true
          description: Esign session client ID (`esign_...`).
          schema:
            type: string
          example: esign_PsLIyRDIHAqyFWhZqhDT
      responses:
        '200':
          description: Signed document download URL.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          url:
                            type: string
                            description: Time-limited URL of the signed PDF.
              example:
                data:
                  url: https://test-esign-bucket.s3.amazonaws.com/...
                status_code: 200
                message: Success
                success: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Esign session not found or not completed yet.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 404
                message: Esign not completed.
                success: false

  /api/v1/templates/save:
    post:
      tags: [Templates]
      operationId: saveTemplate
      summary: Save template
      description: |
        Save a reusable template blueprint for a file. A template stores the
        source `file_id` and an ordered `signers` array, where each signer
        contains its own PDF elements and optional signer-level defaults.

        - `signers` is ordered: the first template signer maps to the first
          runtime signer supplied later in `initialize-from-template`, and so on.
        - Every signer must contain at least one `signature` element.
        - A `field_id` is generated automatically for each saved element and
          returned in template reads; those values are what
          `initialize-from-template.signers[].element_values` uses later.
        - `file_id` must belong to the authenticated user.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SaveTemplateRequest'
            example:
              name: Employment Offer Template
              file_id: file_onwEUoMxnWhewUzodZeW
              folder: HR
              signers:
                - sign_type: suresign
                  auth_mode: 1
                  max_retry: 1
                  reason: Employer sign
                  verify_email: false
                  verify_mobile: false
                  elements:
                    - x: 100
                      y: 120
                      page: 0
                      alignment: center
                      type: signature
                - sign_type: suresign
                  auth_mode: 1
                  reason: Employee sign
                  verify_email: true
                  verify_location: true
                  location_source: ip
                  enforce_geofence: true
                  latitude: 19.076
                  longitude: 72.8777
                  radius: 500
                  elements:
                    - x: 100
                      y: 220
                      page: 0
                      alignment: center
                      type: signature
      responses:
        '200':
          description: Template saved.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/Template'
        '400':
          description: |
            Validation failed. Possible error codes:

            - `bad_request` — Template signer validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 400
                message: bad_request
                success: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: |
            Referenced resource not found. Possible error codes:

            - `not_found` — The provided file ID does not exist for the current user.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 404
                message: not_found
                success: false

  /api/v1/templates/update:
    put:
      tags: [Templates]
      operationId: updateTemplate
      summary: Update template
      description: |
        Update an existing template. `name`, `folder`, `file_id`, and
        `signers` are all optional in the request body.

        - If `signers` is provided, it **replaces** the saved signer blueprint
          completely and new `field_id` values are generated for all elements.
        - If `signers` is omitted, existing signers are preserved.
        - If `file_id` is provided, it must belong to the authenticated user.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTemplateRequest'
            example:
              client_id: template_xxx
              name: Employment Offer Template v2
              folder: HR Updated
              signers:
                - sign_type: suresign
                  max_retry: 2
                  reason: Employer sign updated
                  accept_virtual_sign: true
                  elements:
                    - x: 140
                      y: 120
                      page: 0
                      alignment: center
                      type: signature
      responses:
        '200':
          description: Template updated.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/Template'
        '400':
          description: |
            Validation failed. Possible error codes:

            - `bad_request` — Template signer validation failed when `signers` is provided.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 400
                message: bad_request
                success: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: |
            Referenced resource not found. Possible error codes:

            - `not_found` — The template or provided file ID does not exist.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 404
                message: not_found
                success: false

  /api/v1/esign/initialize-from-template:
    post:
      tags: [Templates]
      operationId: initializeFromTemplate
      summary: Initialize Esign from template
      description: |
        Start an Esign session using a previously saved template. The template
        contributes `file_id`, signer elements, and signer defaults; the
        request contributes top-level Esign options plus runtime signer data
        in the **same order** as the template's `signers` array.

        - Runtime signer count must match the template signer count.
        - `element_values` overrides template element `value`s at runtime by
          template `field_id`; position and type still come from the template.
        - Template signer defaults are merged with runtime signer input before
          the normal `initialize-esign` validation flow runs.
        - `full_name` should be supplied, `reason` must be supplied unless
          saved in the template, and at least one of `email` or `mobile` must
          be present.
        - This endpoint creates a real Esign flow, just like `initialize-esign`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InitializeFromTemplateRequest'
            example:
              template_id: template_xxx
              sign_type: suresign
              auth_mode: 1
              folder: HR
              allow_download: true
              expiry_seconds: 7200
              redirect_url: https://example.com/esign/complete
              email_template_overrides:
                company_name: Acme Corp
              signers:
                - full_name: Employer A
                  email: employer@example.com
                  reason: Employer sign
                  face_ref_file_id: file_faceReferenceImage
                  notification:
                    invite: true
                    completion: true
                    whatsapp: true
                  element_values:
                    field_xxx: INV-1001
                    field_yyy: now
                - full_name: Employee B
                  email: employee@example.com
                  reason: Employee sign
                  name_match: true
                  element_values:
                    field_zzz: Employee B
      responses:
        '200':
          description: Esign session created from the template.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/EsignSession'
              example:
                data:
                  client_id: esign_xxx
                  status: pending
                  file_id: file_onwEUoMxnWhewUzodZeW
                  signers:
                    - signer_id: signer_xxx
                      eligible: true
                      invite_url: https://signer.surepass.app/#token=...
                      sign_type: suresign
                      full_name: Employer A
                      email: employer@example.com
                      reason: Employer sign
                      status: pending
                status_code: 200
                message: Success
                success: true
        '400':
          description: |
            Validation failed. Possible error codes:

            - `signer_count_mismatch` — Runtime signer count does not match the saved template.
            - `bad_request` — Validation failed after template and runtime signer merge.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 400
                message: signer_count_mismatch
                success: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: |
            Referenced resource not found. Possible error codes:

            - `not_found` — The provided template ID or referenced file ID does not exist.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 404
                message: not_found
                success: false

  /api/v1/stamps/state-codes:
    get:
      tags: [Stamps]
      operationId: listStateCodes
      summary: List state codes
      description: List the state codes supported for stamp procurement.
      responses:
        '200':
          description: Supported state codes.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        type: array
                        description: Supported stamp state codes.
                        items:
                          type: string
              example:
                data:
                  - AN
                  - AP
                  - AR
                  - AS
                  - BR
                  - CH
                  - CT
                  - DL
                  - GA
                  - GJ
                  - HR
                  - HP
                  - JH
                  - JK
                  - KA
                  - KL
                  - MH
                  - ML
                  - MN
                  - MP
                  - MZ
                  - OR
                  - PB
                  - PY
                  - RJ
                  - SK
                  - TG
                  - TN
                  - TR
                  - UP
                  - UT
                  - WB
                status_code: 200
                message: Success
                success: true
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/stamps/articles:
    get:
      tags: [Stamps]
      operationId: listArticles
      summary: List articles
      description: |
        List the supported articles for a state. Use the returned `article_id`
        when creating a procurement order.
      parameters:
        - name: state_code
          in: query
          required: true
          description: Supported stamp state code.
          schema:
            type: string
            example: AN
      responses:
        '200':
          description: Articles supported for the state.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/StampArticle'
              example:
                data:
                  - article_id: '2337'
                    article_code: AFFIDAVIT
                    article_name: Affidavit
                status_code: 200
                message: Success
                success: true
        '400':
          description: |
            Validation failed. Possible error codes:

            - `bad_request` — `state_code` is missing or not supported.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 400
                message: bad_request
                success: false
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/stamps/create-order:
    post:
      tags: [Stamps]
      operationId: createStampOrder
      summary: Create stamp order
      description: |
        Create a stamp procurement order and start the background procurement
        workflow.

        - This call is **asynchronous** — a successful response does not mean
          `stamps` is populated yet. Poll `GET /api/v1/stamps/order-status` or
          `GET /api/v1/stamps/list` until instances become `available`.
        - Billing charges `estamp_duty` (`stamp_duty_amount × quantity`) plus
          `estamp_fees` (one per stamp). The request is rejected if the wallet
          balance is insufficient.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateStampOrderRequest'
            example:
              state_code: AN
              first_party_name: Alice Test
              second_party_name: Bob Test
              article_id: '2337'
              stamp_duty_amount: 10
              consideration_amount: 1000.0
              quantity: 1
      responses:
        '200':
          description: Order created; procurement continues in the background.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/StampOrder'
              example:
                data:
                  user_id: user_RQJksjuRzdhRjejyOlhEI
                  client_id: stamp_pkHLyIzspuMWBMXizUSc
                  created_at: '2026-04-18T16:35:21.123000'
                  state_code: AN
                  first_party_name: Alice Test
                  second_party_name: Bob Test
                  article_id: '2337'
                  stamp_duty_amount: '10'
                  consideration_amount: 1000.0
                  description: null
                  first_party_address: null
                  quantity: 1
                  stamps: []
                status_code: 200
                message: Success
                success: true
        '400':
          description: |
            Validation failed. Possible error codes:

            - `bad_request` — invalid `state_code`, `article_id` not valid for
              the state, `stamp_duty_amount` not a whole number or out of range,
              or `quantity` out of range (1–5000).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 400
                message: bad_request
                success: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: Insufficient wallet balance to cover the stamp duty and fees.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 402
                message: Insufficient wallet balance to cover the stamp duty and fees.
                success: false

  /api/v1/stamps/order-status:
    get:
      tags: [Stamps]
      operationId: getStampOrderStatus
      summary: Get order status
      description: |
        Get the latest state of a procurement order. Once the background
        workflow completes, `stamps` contains the vendor stamp instances and
        `status_counts` is populated.

        - `stamps[].stamp_id` is the public stamp **instance** ID — use it with
          `GET /api/v1/stamps/download-pdf` and for explicit stamp reservation
          in Esign initialization.
      parameters:
        - name: client_id
          in: query
          required: true
          description: Stamp procurement order client ID (`stamp_...`).
          schema:
            type: string
            example: stamp_pkHLyIzspuMWBMXizUSc
      responses:
        '200':
          description: Latest stored order state.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/StampOrder'
              example:
                data:
                  user_id: user_RQJksjuRzdhRjejyOlhEI
                  client_id: stamp_pkHLyIzspuMWBMXizUSc
                  created_at: '2026-04-18T16:35:21.123000'
                  state_code: AN
                  first_party_name: Alice Test
                  second_party_name: Bob Test
                  article_id: '2337'
                  stamp_duty_amount: '10'
                  consideration_amount: 1000.0
                  description: null
                  first_party_address: null
                  quantity: 1
                  status_counts:
                    pending: 0
                    available: 1
                    reserved: 0
                    used: 0
                    rejected: 0
                  stamps:
                    - stamp_id: stamp_inst_uNeOUwjMVGNFexnxFSCP
                      status: available
                status_code: 200
                message: Success
                success: true
        '400':
          description: |
            Validation failed. Possible error codes:

            - `bad_request` — `client_id` is missing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 400
                message: bad_request
                success: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: |
            Referenced resource not found. Possible error codes:

            - `not_found` — no procurement order exists with the given `client_id`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 404
                message: not_found
                success: false

  /api/v1/stamps/list:
    get:
      tags: [Stamps]
      operationId: listStampOrders
      summary: List stamp orders
      description: |
        List procurement orders for the authenticated user, paginated.

        When filtering by `status=pending`, matching orders are returned with an
        empty `stamps` array (pending stamps are not yet materialized as
        instances), while `status_counts.pending` reflects the pending count.
      parameters:
        - name: page
          in: query
          required: true
          description: Page number.
          schema:
            type: integer
            example: 1
        - name: size
          in: query
          required: true
          description: Page size.
          schema:
            type: integer
            example: 20
        - name: client_id
          in: query
          required: false
          description: Filter by order client ID.
          schema:
            type: string
        - name: state_code
          in: query
          required: false
          description: Filter by state code.
          schema:
            type: string
        - name: article_id
          in: query
          required: false
          description: Filter by article ID. Requires `state_code`.
          schema:
            type: string
        - name: status
          in: query
          required: false
          description: Filter by stamp instance status.
          schema:
            $ref: '#/components/schemas/StampVendorOrderStatus'
      responses:
        '200':
          description: Paginated procurement orders.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items:
                              $ref: '#/components/schemas/StampOrder'
                          has_next:
                            type: boolean
                            description: Whether more pages are available.
              example:
                data:
                  items:
                    - user_id: user_RQJksjuRzdhRjejyOlhEI
                      client_id: stamp_pkHLyIzspuMWBMXizUSc
                      created_at: '2026-04-18T16:35:21.123000'
                      state_code: AN
                      first_party_name: Alice Test
                      second_party_name: Bob Test
                      article_id: '2337'
                      stamp_duty_amount: '10'
                      consideration_amount: 1000.0
                      description: null
                      first_party_address: null
                      quantity: 1
                      status_counts:
                        pending: 0
                        available: 1
                        reserved: 0
                        used: 0
                        rejected: 0
                      stamps:
                        - stamp_id: stamp_inst_vPqJLShuVXRtStskqsCw
                          status: available
                  has_next: true
                status_code: 200
                message: Success
                success: true
        '400':
          description: |
            Validation failed. Possible error codes:

            - `bad_request` — invalid `page` or `size`, or `article_id` provided
              without `state_code`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 400
                message: bad_request
                success: false
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/stamps/download-pdf:
    get:
      tags: [Stamps]
      operationId: downloadStampPdf
      summary: Download stamp PDF
      description: |
        Get the vendor-provided download URL for a completed stamp PDF. PDF
        bytes are not streamed through the API — the response contains a
        short-lived signed URL.

        Pass the stamp **instance** ID (`stamps[].stamp_id` from order-status
        or list), not the order ID.
      parameters:
        - name: client_id
          in: query
          required: true
          description: Stamp instance client ID (`stamp_inst_...`).
          schema:
            type: string
            example: stamp_inst_uNeOUwjMVGNFexnxFSCP
      responses:
        '200':
          description: Signed download URL for the stamp PDF.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          stamp_id:
                            type: string
                            description: The requested stamp instance client ID.
                          url:
                            type: string
                            description: Signed, time-limited download URL for the stamp PDF.
                          expires_in:
                            type: integer
                            description: Seconds until the URL expires.
                          page_count:
                            type: ['integer', 'null']
                            description: Number of pages in the stamp PDF, if known.
              example:
                data:
                  stamp_id: stamp_inst_uNeOUwjMVGNFexnxFSCP
                  url: https://example-bucket.s3.amazonaws.com/sample-estamp.pdf?X-Amz-Signature=...
                  expires_in: 3600
                  page_count: 1
                status_code: 200
                message: Success
                success: true
        '400':
          description: |
            Validation failed. Possible error codes:

            - `bad_request` — `client_id` is missing, or the stamp is not
              completed yet.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 400
                message: bad_request
                success: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: |
            Referenced resource not found. Possible error codes:

            - `not_found` — no stamp instance exists with the given `client_id`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                data: null
                status_code: 404
                message: not_found
                success: false

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Bearer token authentication — paste your access token below.

  responses:
    Unauthorized:
      description: |
        Authentication failed. Possible error codes:

        - `unauthorized` — Invalid or expired access token.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            data: null
            status_code: 401
            message: unauthorized
            success: false

  schemas:
    ApiEnvelope:
      type: object
      description: Standard response envelope.
      properties:
        status_code:
          type: integer
          description: HTTP status code echoed in the response body.
          example: 200
        message:
          type: string
          description: Human-readable result message.
          example: Success
        success:
          type: boolean
          description: '`true` when the request succeeded.'
          example: true

    ErrorEnvelope:
      type: object
      properties:
        data:
          type: 'null'
          description: Always `null` for error responses.
        status_code:
          type: integer
          description: HTTP status code echoed in the response body.
        message:
          type: string
          description: Error code identifying the failure — see Possible Error Codes.
        success:
          type: boolean
          description: Always `false` for error responses.
          example: false

    UploadedFile:
      type: object
      properties:
        user_id:
          type: string
          description: ID of the user that owns the file.
          example: user_xxx
        client_id:
          type: string
          description: File ID to reference in later requests.
          example: file_xxx
        name:
          type: string
          description: Original file name.
          example: Form_26.pdf
        mime:
          type: string
          description: MIME type of the uploaded file.
          example: application/pdf

    SignType:
      type: string
      enum: [aadhaar, suresign, pfx]
      description: |
        `aadhaar` — OTP-based Aadhaar Esign. `suresign` — auto-signing by
        Surepass. `pfx` — PFX-certificate signing (requires `pfx_id`).

    ElementType:
      type: string
      enum: [signature, image, text, date, initials]
      description: |
        `signature` — signature input box. `image` — preloaded image
        (uploaded as asset). `text` — text field. `date` — date field with
        formatting support. `initials` — initials block.

    Element:
      type: object
      required: [x, y, page, type]
      properties:
        x:
          type: number
          description: X position on the page.
        y:
          type: number
          description: Y position on the page.
        page:
          type: integer
          minimum: 0
          description: 'Zero-indexed page number: 0 is the first page.'
        type:
          $ref: '#/components/schemas/ElementType'
        alignment:
          type: string
          description: Alignment of the element at its position.
          example: center
        width:
          type: number
          description: Element width.
        height:
          type: number
          description: Element height.
        font_size:
          type: number
          description: Font size for `text` and `date` elements.
        format:
          type: string
          description: Date format for `date` elements.
        value:
          type: string
          description: |
            Element value — text content for `text` elements, an asset file
            ID (from `POST /api/v1/files/upload-asset`) for `image` elements,
            or a wet-signature asset ID for `signature` elements when
            `allow_wet_signature` is enabled.
        allow_wet_signature:
          type: boolean
          default: false
          description: |
            Per `signature` element flag. When `false`, any signature `value`
            asset ID is stripped during request parsing. Set to `true` only
            for signature fields that intentionally allow a wet-signature
            image.

    Notification:
      type: object
      description: |
        Per-signer notification controls. Omit to keep `invite` and
        `completion` enabled and `whatsapp` disabled.
      properties:
        invite:
          type: boolean
          default: true
          description: Send signer invite communication by email/SMS.
        completion:
          type: boolean
          default: true
          description: Send completed-document communication after the Esign finishes successfully.
        whatsapp:
          type: boolean
          default: false
          description: |
            Also deliver the invite and the completed signed document over
            WhatsApp. Requires the signer to have a `mobile` (with
            `country_code`); silently ignored otherwise. Only sends when
            `invite`/`completion` (respectively) are also enabled.

    EmailTemplateOverrides:
      type: object
      description: |
        Optional invite email branding overrides. When `company_name` is
        provided, the default Surepass logo and branding are hidden, allowing
        white-label emails.
      properties:
        company_name:
          type: string
          description: Custom company name shown in email.
        legal_name:
          type: string
          description: 'Legal entity name shown in footer (default: "Surepass Technologies Pvt Ltd.").'
        support_email:
          type: string
          description: 'Support email shown in footer (default: "techsupport@surepass.io").'

    Signer:
      type: object
      required: [elements]
      properties:
        full_name:
          type: string
        email:
          type: string
          description: Required if `mobile` is not provided.
        mobile:
          type: string
          description: Required if `email` is not provided.
        country_code:
          type: string
          description: Required when `mobile` is provided.
          example: '+91'
        pfx_id:
          type: string
          description: PFX certificate ID. Required when `sign_type` is `pfx`.
        reason:
          type: string
          description: Reason for signing, shown on the signature.
        verify_email:
          type: boolean
          description: Require email OTP verification before signing.
        verify_mobile:
          type: boolean
          description: Require mobile OTP verification before signing.
        name_match:
          type: boolean
          description: |
            Aadhaar signers only. If `true`, the backend compares the signer
            `full_name` with the Aadhaar certificate name and returns a fuzzy
            `score` from 0 to 100. Ignored for non-Aadhaar signers.
        face_ref_file_id:
          type: string
          description: |
            Reference image file ID. Providing it enables face verification
            for this signer. Upload the JPEG/PNG through
            `POST /api/v1/files/upload-asset` first and pass the returned
            `client_id`.
        face_match_threshold:
          type: integer
          minimum: 50
          maximum: 100
          default: 80
          description: Minimum face-match score. Only enforced when `face_ref_file_id` is provided.
        face_max_attempts:
          type: integer
          minimum: 1
          maximum: 10
          default: 5
          description: Maximum face-verification attempts. Only enforced when `face_ref_file_id` is provided.
        notification:
          $ref: '#/components/schemas/Notification'
        elements:
          type: array
          items:
            $ref: '#/components/schemas/Element'

    InitializeEsignRequest:
      type: object
      required: [sign_type, file_id, signers]
      properties:
        sign_type:
          $ref: '#/components/schemas/SignType'
        auth_mode:
          type: integer
          description: Authentication mode for the signing session.
          example: 1
        file_id:
          type: string
          description: File ID returned by `POST /api/v1/files/upload-pdf`.
        folder:
          type: string
          description: Folder name to organize the session under.
        expiry_seconds:
          type: integer
          minimum: 3600
          maximum: 2592000
          description: Session expiry. Min 3600 (1 hour), max 2592000 (30 days).
        email_template_overrides:
          $ref: '#/components/schemas/EmailTemplateOverrides'
        stamp:
          $ref: '#/components/schemas/Stamp'
        signers:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/Signer'

    NameMatchResult:
      type: object
      properties:
        enforce:
          type: boolean
          description: '`true` when name matching was requested at initialization.'
        prefill_name:
          type: [string, 'null']
          description: Name from the Aadhaar certificate.
        signer_name:
          type: [string, 'null']
          description: '`full_name` supplied at initialization.'
        score:
          type: [number, 'null']
          description: Fuzzy match score from 0 to 100.
        status:
          type: string
          enum: [pending, completed, skipped]
          description: |
            `pending` — waiting for Aadhaar certificate details.
            `completed` — score calculated. `skipped` — not requested.

    FaceMatchResult:
      type: object
      properties:
        enforce:
          type: boolean
          description: '`true` when initialization supplied `face_ref_file_id`.'
        ref_file_id:
          type: [string, 'null']
          description: Reference image file ID.
        live_file_id:
          type: [string, 'null']
          description: Successfully verified live image file ID.
        match_threshold:
          type: integer
          description: Minimum face-match score required to pass.
        max_attempts:
          type: integer
          description: Maximum face-verification attempts allowed.
        liveness_score:
          type: [number, 'null']
          description: Remains `null` until face verification succeeds.
        match_score:
          type: [number, 'null']
          description: Remains `null` until face verification succeeds.

    AadhaarCertDetails:
      type: object
      description: |
        Present for Aadhaar signers after Aadhaar signing completes; `null`
        for non-Aadhaar signers and pending Aadhaar signers.
      properties:
        name:
          type: string
          description: Name on the Aadhaar certificate.
        country:
          type: string
          description: Country from the certificate.
        state:
          type: string
          description: State from the certificate.
        pin_code:
          type: string
          description: PIN code from the certificate.
        aadhaar:
          type: string
          description: Last digits of the Aadhaar number.
        year_of_birth:
          type: string
          description: Year of birth from the certificate.
        gender:
          type: string
          description: Gender from the certificate.

    SignerStatus:
      type: object
      properties:
        signer_id:
          type: string
          description: Unique signer ID.
          example: signer_vxgTyyOxYDhkhsIKQtda
        full_name:
          type: string
          description: Signer's full name.
        email:
          type: [string, 'null']
          description: Signer email (`null` for mobile-only signers).
        mobile:
          type: [string, 'null']
          description: Signer mobile (`null` for email-only signers).
        status:
          type: string
          description: Signer status.
          example: pending
        aadhaar_cert_details:
          oneOf:
            - $ref: '#/components/schemas/AadhaarCertDetails'
            - type: 'null'
        name_match:
          $ref: '#/components/schemas/NameMatchResult'
        face_match:
          $ref: '#/components/schemas/FaceMatchResult'
        positions:
          type: object
          description: Signature positions per page (1-indexed page keys).
          additionalProperties:
            type: array
            items:
              type: object
              properties:
                x:
                  type: number
                y:
                  type: number

    EsignSession:
      type: object
      properties:
        client_id:
          type: string
          description: Esign session ID (`esign_...`).
        status:
          type: string
          description: Overall session status.
          example: pending
        file_id:
          type: string
          description: File ID of the PDF being signed.
        signers:
          type: array
          description: Runtime signer states, including invite URLs and signing tokens.
          items:
            $ref: '#/components/schemas/SessionSigner'

    SessionSigner:
      type: object
      description: |
        Runtime signer state returned when a session is created. Includes
        `invite_url` and `token` used by the signer to open the signing page.
      properties:
        signer_id:
          type: string
          description: Unique signer ID.
          example: signer_xxx
        eligible:
          type: boolean
          description: Whether it is currently this signer's turn to sign.
        invite_url:
          type: string
          description: Link the signer opens to sign the document.
          example: https://signer.surepass.app/#token=...
        token:
          type: string
          description: Signing token embedded in the invite URL.
        sign_type:
          $ref: '#/components/schemas/SignType'
        auth_mode:
          type: array
          description: Allowed authentication modes for the signer.
          items:
            type: integer
          example: [1]
        max_retry:
          type: [integer, 'null']
          description: Maximum signing retries (`null` = default).
        pfx_id:
          type: [string, 'null']
          description: PFX certificate ID (`null` unless PFX signing).
        full_name:
          type: string
          description: Signer's full name.
        email:
          type: [string, 'null']
          description: Signer email (`null` for mobile-only signers).
        country_code:
          type: [string, 'null']
          description: Country code for the signer's mobile number.
        mobile:
          type: [string, 'null']
          description: Signer mobile (`null` for email-only signers).
        verify_email:
          type: boolean
          description: Email OTP verification required before signing.
        verify_mobile:
          type: boolean
          description: Mobile OTP verification required before signing.
        reason:
          type: string
          description: Reason for signing, shown on the signature.
        accept_virtual_sign:
          type: boolean
          description: Whether virtual (drawn) signatures are accepted.
        status:
          type: string
          description: Signer status.
          example: pending
        aadhaar_cert_details:
          oneOf:
            - $ref: '#/components/schemas/AadhaarCertDetails'
            - type: 'null'
        name_match:
          $ref: '#/components/schemas/NameMatchResult'
        face_match:
          $ref: '#/components/schemas/FaceMatchResult'
        elements:
          type: array
          description: Elements assigned to this signer.
          items:
            $ref: '#/components/schemas/TemplateElement'
        verify_location:
          type: boolean
          description: Whether the signer's location is required during signing.
        location_source:
          type: string
          enum: [device, ip]
          description: Where the signer's location is read from.
        enforce_geofence:
          type: boolean
          description: Whether signing is restricted to a geographic area.
        latitude:
          type: [number, 'null']
          description: Geofence latitude (`null` when not enforced).
        longitude:
          type: [number, 'null']
          description: Geofence longitude (`null` when not enforced).
        radius:
          type: [number, 'null']
          description: Geofence radius in meters (`null` when not enforced).

    EsignDetails:
      type: object
      properties:
        user_id:
          type: string
          description: ID of the user that owns the session.
        client_id:
          type: string
          description: Esign session ID (`esign_...`).
        created_at:
          type: string
          format: date-time
          description: Session creation timestamp.
        sign_type:
          $ref: '#/components/schemas/SignType'
        file_id:
          type: string
          description: File ID of the PDF being signed.
        allow_download:
          type: boolean
          description: Whether the signed document can be downloaded.
        expiry_seconds:
          type: integer
          description: Session expiry in seconds.
        status:
          type: string
          description: Overall session status.
          example: completed
        signer_idx:
          type: integer
          description: Index of the signer whose turn it is to sign.
        signers:
          type: array
          description: Per-signer status entries.
          items:
            $ref: '#/components/schemas/SignerStatus'

    TemplateElementInput:
      type: object
      required: [x, y, page, alignment, type]
      properties:
        x:
          type: number
          description: X position on the page.
        y:
          type: number
          description: Y position on the page.
        page:
          type: integer
          minimum: 0
          description: 'Zero-indexed page number: 0 is the first page.'
        alignment:
          type: string
          description: Alignment of the element at its position.
          example: center
        type:
          $ref: '#/components/schemas/ElementType'

    TemplateElement:
      allOf:
        - $ref: '#/components/schemas/TemplateElementInput'
        - type: object
          properties:
            field_id:
              type: string
              description: |
                Generated automatically for each saved template element. Use
                these values as keys in
                `initialize-from-template.signers[].element_values`.
              example: field_xxx
            width:
              type: [number, 'null']
              description: Element width (`null` when unset).
            height:
              type: [number, 'null']
              description: Element height (`null` when unset).
            font_size:
              type: [number, 'null']
              description: Font size for `text` and `date` elements (`null` when unset).
            format:
              type: [string, 'null']
              description: Date format for `date` elements (`null` when unset).
            value:
              type: [string, 'null']
              description: Saved element value (`null` when unset).

    TemplatEsigner:
      type: object
      required: [elements]
      description: |
        Signer blueprint stored in a template. All defaults are optional and
        are merged with runtime signer input when initializing from the
        template. Every signer must contain at least one `signature` element.
      properties:
        sign_type:
          type: string
          enum: [aadhaar, suresign, pfx]
          description: |
            `aadhaar` — OTP-based Aadhaar Esign. `suresign` — auto-signing by
            Surepass. `pfx` — PFX-certificate signing (requires `pfx_id`).
        auth_mode:
          oneOf:
            - type: integer
            - type: array
              items:
                type: integer
          description: |
            Authentication mode(s). Requests accept a single integer;
            responses return an array.
          example: 1
        max_retry:
          type: integer
          description: Maximum signing retries.
        pfx_id:
          type: string
          description: PFX certificate ID for PFX-based signing.
        reason:
          type: string
          description: Default reason for signing, shown on the signature.
        verify_email:
          type: boolean
          description: Require email OTP verification before signing.
        verify_mobile:
          type: boolean
          description: Require mobile OTP verification before signing.
        accept_virtual_sign:
          type: boolean
          description: Allow virtual (drawn) signatures.
        verify_location:
          type: boolean
          description: Require the signer's location during signing.
        location_source:
          type: string
          enum: [device, ip]
          description: Where the signer's location is read from.
        enforce_geofence:
          type: boolean
          description: |
            Restrict signing to a geographic area. When `true`, `latitude`,
            `longitude`, and `radius` are required together.
        latitude:
          type: number
          description: Required together with `longitude` and `radius` when `enforce_geofence` is `true`.
        longitude:
          type: number
          description: Required together with `latitude` and `radius` when `enforce_geofence` is `true`.
        radius:
          type: number
          description: Geofence radius in meters. Required together with `latitude` and `longitude` when `enforce_geofence` is `true`.
        elements:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/TemplateElementInput'

    SaveTemplateRequest:
      type: object
      required: [name, file_id, signers]
      properties:
        name:
          type: string
          description: Template name.
          example: Employment Offer Template
        file_id:
          type: string
          description: Must belong to the authenticated user.
        folder:
          type: string
          description: Folder name to organize the template under.
          example: HR
        signers:
          type: array
          minItems: 1
          description: |
            Ordered array — the first template signer maps to the first
            runtime signer in `initialize-from-template`, and so on.
          items:
            $ref: '#/components/schemas/TemplatEsigner'

    UpdateTemplateRequest:
      type: object
      required: [client_id]
      properties:
        client_id:
          type: string
          description: Template ID to update (`template_...`).
          example: template_xxx
        file_id:
          type: string
          description: If provided, must belong to the authenticated user.
        name:
          type: string
          description: New template name.
        folder:
          type: string
          description: New folder name.
        signers:
          type: array
          description: |
            If provided, replaces the saved signer blueprint completely and
            regenerates all `field_id` values. If omitted, existing signers
            are preserved.
          items:
            $ref: '#/components/schemas/TemplatEsigner'

    Template:
      type: object
      properties:
        client_id:
          type: string
          description: Template ID (`template_...`).
          example: template_xxx
        name:
          type: string
          description: Template name.
        file_id:
          type: string
          description: File ID of the template's base PDF.
        folder:
          type: string
          description: Folder the template is organized under.
        signers:
          type: array
          description: Saved signer blueprint, including generated `field_id` values.
          items:
            allOf:
              - $ref: '#/components/schemas/TemplatEsigner'
              - type: object
                properties:
                  elements:
                    type: array
                    items:
                      $ref: '#/components/schemas/TemplateElement'

    Stamp:
      type: object
      description: |
        Optional e-stamping configuration. Use either the amount-based
        reservation flow (`amount` + `state_code` + `article_id`, required
        together) or pass pre-reserved `stamp_instance_ids`.
      properties:
        amount:
          type: string
          description: Stamp amount to reserve.
          example: '100.00'
        state_code:
          type: string
          description: State code for stamp duty.
          example: MH
        article_id:
          type: string
          description: Stamp article ID.
          example: article_001
        first_party_name:
          type: string
          description: First party name printed on the stamp.
        second_party_name:
          type: string
          description: Second party name printed on the stamp.
        stamp_instance_ids:
          type: array
          items:
            type: string
          description: Alternative to the `amount`/`state_code`/`article_id` flow.

    RuntimeTemplatEsigner:
      type: object
      description: |
        Runtime signer data supplied in the same order as the template's
        `signers` array. At least one of `email` or `mobile` must be present.
      properties:
        full_name:
          type: string
          description: Signer's full name.
        email:
          type: string
          description: Required if `mobile` is not provided.
        mobile:
          type: string
          description: Required if `email` is not provided.
        country_code:
          type: string
          description: Required when `mobile` is provided.
          example: '+91'
        reason:
          type: string
          description: Required unless a default reason is already saved in the template signer.
        name_match:
          type: boolean
          description: Aadhaar signers only; ignored otherwise.
        face_ref_file_id:
          type: string
          description: Enables face verification. Upload the JPEG/PNG via `POST /api/v1/files/upload-asset` first.
        face_match_threshold:
          type: integer
          minimum: 50
          maximum: 100
          default: 80
          description: Minimum face-match score. Only enforced when `face_ref_file_id` is provided.
        face_max_attempts:
          type: integer
          minimum: 1
          maximum: 10
          default: 5
          description: Maximum face-verification attempts. Only enforced when `face_ref_file_id` is provided.
        notification:
          $ref: '#/components/schemas/Notification'
        element_values:
          type: object
          additionalProperties:
            type: string
          description: |
            Values to apply to saved template elements, keyed by the
            template's generated `field_id` values. Only overrides element
            `value`; position and type still come from the template. Signature
            `value` asset IDs are stripped unless the saved field sets
            `allow_wet_signature: true`.

    InitializeFromTemplateRequest:
      type: object
      required: [template_id, signers]
      properties:
        template_id:
          type: string
          description: Template ID returned by `POST /api/v1/templates/save`.
          example: template_xxx
        sign_type:
          $ref: '#/components/schemas/SignType'
        auth_mode:
          type: integer
          description: Authentication mode for the signing session.
          example: 1
        max_retry:
          type: integer
          description: Maximum signing retries.
        folder:
          type: string
          description: Folder name to organize the session under.
        allow_download:
          type: boolean
          description: Allow downloading the signed document.
        expiry_seconds:
          type: integer
          minimum: 3600
          maximum: 2592000
          description: Optional. Min 3600 (1 hour), max 2592000 (30 days).
        redirect_url:
          type: string
          description: URL the signer is redirected to after completing the Esign.
        backend:
          type: string
          description: Signing backend to use.
          example: emudhra
        stamp:
          $ref: '#/components/schemas/Stamp'
        email_template_overrides:
          $ref: '#/components/schemas/EmailTemplateOverrides'
        signers:
          type: array
          minItems: 1
          description: Count must match the template signer count, in the same order.
          items:
            $ref: '#/components/schemas/RuntimeTemplatEsigner'

    StampVendorOrderStatus:
      type: string
      description: |
        Lifecycle status of a stamp instance: `pending` (procurement not
        completed yet), `available` (issued and usable), `reserved`
        (temporarily held by another flow), `used` (already consumed), or
        `rejected` (procurement rejected by the vendor).
      enum: [pending, available, reserved, used, rejected]

    StampStatusCounts:
      type: object
      description: Per-status counts of stamp instances in a procurement order.
      properties:
        pending:
          type: integer
          description: Stamps still pending with the vendor (includes seeded pending rows).
          example: 0
        available:
          type: integer
          description: Available stamp instances.
          example: 1
        reserved:
          type: integer
          description: Reserved stamp instances.
          example: 0
        used:
          type: integer
          description: Used stamp instances.
          example: 0
        rejected:
          type: integer
          description: Rejected stamp instances.
          example: 0

    StampInstance:
      type: object
      description: A single materialized stamp within a procurement order.
      properties:
        stamp_id:
          type: string
          description: |
            Public stamp instance client ID. Use it with
            `GET /api/v1/stamps/download-pdf` and for explicit stamp
            reservation in Esign initialization.
          example: stamp_inst_uNeOUwjMVGNFexnxFSCP
        status:
          $ref: '#/components/schemas/StampVendorOrderStatus'

    StampArticle:
      type: object
      description: A stamp article supported by a state.
      properties:
        article_id:
          type: string
          description: Article ID used when creating a procurement order.
          example: '2337'
        article_code:
          type: string
          description: Vendor article code.
          example: AFFIDAVIT
        article_name:
          type: string
          description: Human-readable article name.
          example: Affidavit

    CreateStampOrderRequest:
      type: object
      required:
        - state_code
        - first_party_name
        - second_party_name
        - article_id
        - stamp_duty_amount
        - consideration_amount
        - quantity
      properties:
        state_code:
          type: string
          description: Supported stamp state code.
          example: AN
        first_party_name:
          type: string
          minLength: 1
          maxLength: 200
          description: First party name printed on the stamp.
          example: Alice Test
        second_party_name:
          type: string
          minLength: 1
          maxLength: 200
          description: Second party name printed on the stamp.
          example: Bob Test
        article_id:
          type: string
          minLength: 1
          maxLength: 100
          description: Article ID; must be valid for `state_code`.
          example: '2337'
        stamp_duty_amount:
          type: number
          description: |
            Stamp duty per stamp. Must be a whole number, greater than 0 and at
            most 1,000,000 — fractional values are rejected.
          example: 10
        consideration_amount:
          type: number
          exclusiveMinimum: 0
          description: Consideration amount for the underlying transaction.
          example: 1000.0
        quantity:
          type: integer
          minimum: 1
          maximum: 5000
          description: Number of stamps to procure.
          example: 1
        description:
          type: string
          maxLength: 500
          description: Optional order description.
        first_party_address:
          type: string
          minLength: 1
          maxLength: 500
          description: Optional first party address.

    StampOrder:
      type: object
      description: A stamp procurement order and its materialized instances.
      properties:
        user_id:
          type: string
          example: user_RQJksjuRzdhRjejyOlhEI
        client_id:
          type: string
          description: Procurement order client ID.
          example: stamp_pkHLyIzspuMWBMXizUSc
        created_at:
          type: string
          description: Order creation timestamp.
          example: '2026-04-18T16:35:21.123000'
        state_code:
          type: string
          example: AN
        first_party_name:
          type: string
          example: Alice Test
        second_party_name:
          type: string
          example: Bob Test
        article_id:
          type: string
          example: '2337'
        stamp_duty_amount:
          type: string
          description: Stamp duty per stamp, echoed as a string.
          example: '10'
        consideration_amount:
          type: number
          example: 1000.0
        description:
          type: ['string', 'null']
          description: Optional order description.
        first_party_address:
          type: ['string', 'null']
          description: Optional first party address.
        quantity:
          type: integer
          example: 1
        status_counts:
          $ref: '#/components/schemas/StampStatusCounts'
        stamps:
          type: array
          description: Materialized stamp instances; empty until procurement completes.
          items:
            $ref: '#/components/schemas/StampInstance'
