openapi: 3.1.0
info:
  title: SignYu Aadhaar eSign API
  version: 1.0.0
  summary: Send PDFs for Aadhaar OTP based eSignature from your backend.
  description: |
    The SignYu REST API lets you upload a PDF, add signers, send it for
    Aadhaar eSignature, track progress and download the completion certificate.

    Source of truth: `src/app/api/v1/**` in the SignYu codebase. Keep this file
    in sync with the routes, the Node and Python SDKs (`sdks/`), the Postman
    collection and the docs at https://signyu.com/docs.

    Official SDKs: `npm install signyu` and `pip install signyu`.
  contact:
    name: SignYu
    email: contact@mail.signyu.com
    url: https://signyu.com/api
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
externalDocs:
  description: SignYu API documentation
  url: https://signyu.com/docs
servers:
  - url: https://signyu.com
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Documents
    description: Create documents, add signers, send and track them.

paths:
  /api/v1/documents:
    post:
      operationId: createDocument
      tags: [Documents]
      summary: Create a document
      description: |
        Uploads a PDF and creates a document in `PENDING` state. Send the
        request as `multipart/form-data`. The `file` part must have content
        type `application/pdf` and be at most 10MB.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: The PDF to sign (`application/pdf`, at most 10MB, not empty).
                name:
                  type: string
                  description: Label for the document. Defaults to the uploaded file name, or `Untitled document`.
            encoding:
              file:
                contentType: application/pdf
      responses:
        "201":
          description: Document created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreatedDocument"
              example:
                documentId: b6b1f0e2-1c9a-4a1e-9b1a-2f3d4e5a6b7c
                name: Service Agreement
                status: PENDING
        "400":
          description: "`invalid_content_type` (not multipart), `invalid_request` (no `file` field) or `invalid_file` (not a PDF, or empty)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: invalid_file
                message: Only PDF files are supported.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ApiAccessNotEnabled"
        "413":
          description: "`file_too_large`: the PDF is over 10MB."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: file_too_large
                message: PDF must be at most 10MB.
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    get:
      operationId: listDocuments
      tags: [Documents]
      summary: List documents
      description: Returns your documents, most recent first, with a summary of signer progress.
      parameters:
        - name: limit
          in: query
          required: false
          description: Number of documents to return. Values outside 1 to 100 are clamped; invalid values fall back to 20.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: offset
          in: query
          required: false
          description: Number of documents to skip.
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        "200":
          description: A page of documents.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentList"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ApiAccessNotEnabled"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/v1/documents/{documentId}:
    parameters:
      - $ref: "#/components/parameters/DocumentId"
    get:
      operationId: getDocument
      tags: [Documents]
      summary: Retrieve a document
      description: |
        Returns the document, each signer's progress and, once `COMPLETED`, a
        temporary presigned `downloadUrl` for the signed PDF plus the
        `certificateUrl` endpoint. Each signer's `signUrl` is `null` while the
        document is `PENDING`.
      responses:
        "200":
          description: The document.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Document"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ApiAccessNotEnabled"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/v1/documents/{documentId}/signers:
    parameters:
      - $ref: "#/components/parameters/DocumentId"
    post:
      operationId: addSigners
      tags: [Documents]
      summary: Add signers
      description: |
        Adds one or more signers. Only allowed while the document is
        `PENDING`. Signers sign in the order they are added. A document can
        have at most 6 signers in total.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AddSignersRequest"
            example:
              signers:
                - name: Asha Rao
                  phone: "9876543210"
                  email: asha@example.com
                - name: Vikram Nair
                  phone: "9812345678"
                  email: vikram@example.com
                  advanced:
                    signaturePlacement:
                      positions:
                        - { page: 2, x: 31, y: 257, width: 253, height: 110 }
      responses:
        "201":
          description: Signers created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AddSignersResponse"
        "400":
          description: "`invalid_request` (validation failed, `message` names the first problem) or `signer_limit_reached` (more than 6 signers in total)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: signer_limit_reached
                message: A document can have at most 6 signers.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ApiAccessNotEnabled"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "`invalid_state`: the document has already been sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: invalid_state
                message: Signers can only be added before the document is sent.
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/v1/documents/{documentId}/send:
    parameters:
      - $ref: "#/components/parameters/DocumentId"
    post:
      operationId: sendDocument
      tags: [Documents]
      summary: Send for signature
      description: |
        Deducts one credit per signer, marks the document `SENT`, emails a
        signing link to each signer and returns those links. Requires at least
        one signer. No request body. Not idempotent: a second call returns
        `409 invalid_state`.
      responses:
        "200":
          description: Document sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SendDocumentResponse"
        "400":
          description: "`no_signers`: add at least one signer before sending."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: no_signers
                message: Add at least one signer before sending.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: "`insufficient_credits`: not enough credits for every signer. Nothing is sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: insufficient_credits
                message: You need 2 credits to send this document.
        "403":
          $ref: "#/components/responses/ApiAccessNotEnabled"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "`invalid_state`: the document has already been sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: invalid_state
                message: This document has already been sent.
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /api/v1/documents/{documentId}/certificate:
    parameters:
      - $ref: "#/components/parameters/DocumentId"
    get:
      operationId: getCertificate
      tags: [Documents]
      summary: Download completion certificate
      description: |
        Returns a PDF containing the Signature Completion Certificate and the
        chronological audit trail, generated on demand. Only available once
        the document is `COMPLETED`.
      responses:
        "200":
          description: "The certificate PDF (sent as an attachment, `Cache-Control: no-store`)."
          headers:
            Content-Disposition:
              description: '`attachment; filename="<name>.pdf"`'
              schema:
                type: string
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ApiAccessNotEnabled"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "`invalid_state`: the document is not yet `COMPLETED`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: invalid_state
                message: Completion certificate is only available after all signatures are collected
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

webhooks:
  signer.signed:
    post:
      operationId: signerSignedWebhook
      summary: A signer has completed signing
      description: |
        Sent to every enabled webhook endpoint on your account when one signer
        finishes signing. Verify `X-SignSetu-Signature` before trusting the
        body: it is `sha256=` followed by the lowercase hex HMAC-SHA256 of the
        raw request body, keyed with the endpoint's signing secret
        (`whsec_...`). There is no timestamp in the signature. Return any 2xx
        within 10 seconds; failures are retried with backoff, so handlers must
        be idempotent.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookSignatureHeader"
        - $ref: "#/components/parameters/WebhookEventHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignerSignedEvent"
      responses:
        "200":
          description: Any 2xx acknowledges receipt.
  document.completed:
    post:
      operationId: documentCompletedWebhook
      summary: Every signer has signed
      description: |
        Sent once the document is fully signed. Signed the same way as
        `signer.signed`. `certificateUrl` is the Bearer-authenticated
        certificate endpoint.
      security: []
      parameters:
        - $ref: "#/components/parameters/WebhookSignatureHeader"
        - $ref: "#/components/parameters/WebhookEventHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DocumentCompletedEvent"
      responses:
        "200":
          description: Any 2xx acknowledges receipt.

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: sk_live_...
      description: "Your API key from the dashboard (Developers), sent as `Authorization: Bearer sk_live_...`. Requires an API access subscription."

  parameters:
    DocumentId:
      name: documentId
      in: path
      required: true
      description: The document ID returned when it was created.
      schema:
        type: string
        format: uuid
    WebhookSignatureHeader:
      name: X-SignSetu-Signature
      in: header
      required: true
      description: "`sha256=<hex>`: HMAC-SHA256 of the raw body with your endpoint secret."
      schema:
        type: string
        pattern: "^sha256=[0-9a-f]{64}$"
    WebhookEventHeader:
      name: X-SignSetu-Event
      in: header
      required: true
      description: The event type.
      schema:
        $ref: "#/components/schemas/WebhookEventType"

  responses:
    Unauthorized:
      description: "`unauthorized`: the API key is missing or invalid."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: unauthorized
            message: Invalid API key.
    ApiAccessNotEnabled:
      description: "`api_access_not_enabled`: the account has no API access subscription."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: api_access_not_enabled
            message: This account does not have API access. Subscribe at /app/subscribe-api.
    NotFound:
      description: "`not_found`: the document does not exist or is not owned by your account."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: not_found
            message: The specified document does not exist.
    RateLimited:
      description: Too many requests. Rejected at the edge, so the body may not be the JSON error shape. Retry with backoff.
    InternalError:
      description: "`internal_error`: something went wrong on our side."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: internal_error
            message: Failed to send document.

  schemas:
    Error:
      type: object
      required: [error, message]
      properties:
        error:
          type: string
          description: Machine readable error code.
          enum:
            - invalid_content_type
            - invalid_request
            - invalid_file
            - no_signers
            - signer_limit_reached
            - unauthorized
            - insufficient_credits
            - api_access_not_enabled
            - not_found
            - invalid_state
            - file_too_large
            - internal_error
        message:
          type: string
          description: Human readable explanation.

    DocumentStatus:
      type: string
      description: "`PENDING` (created, not sent), `SENT` (out for signature), `COMPLETED` (every signer has signed). `FAILED` and `CANCELLED` are reserved."
      enum: [PENDING, SENT, COMPLETED, FAILED, CANCELLED]

    SignaturePosition:
      type: object
      required: [page, x, y, width, height]
      properties:
        page:
          type: integer
          minimum: 1
          description: 1-based page index.
        x:
          type: number
          description: Left edge in PDF points.
        y:
          type: number
          description: Bottom edge in PDF points (origin is the bottom-left of the page).
        width:
          type: number
          minimum: 140
          description: Box width in PDF points.
        height:
          type: number
          minimum: 110
          description: Box height in PDF points.

    SignaturePlacement:
      type: object
      required: [positions]
      properties:
        positions:
          type: array
          minItems: 1
          maxItems: 20
          description: One rectangle per page. Duplicate pages are rejected.
          items:
            $ref: "#/components/schemas/SignaturePosition"

    SignerAdvanced:
      type: object
      additionalProperties: false
      properties:
        signaturePlacement:
          $ref: "#/components/schemas/SignaturePlacement"

    SignerPlacementEcho:
      type: object
      required: [signaturePlacement]
      properties:
        signaturePlacement:
          $ref: "#/components/schemas/SignaturePlacement"

    SignerInput:
      type: object
      required: [name, phone, email]
      properties:
        name:
          type: string
          minLength: 1
        phone:
          type: string
          minLength: 10
          pattern: "^\\d+$"
          description: Digits only, at least 10.
        email:
          type: string
          format: email
          description: The signing link is emailed here.
        advanced:
          $ref: "#/components/schemas/SignerAdvanced"

    AddSignersRequest:
      type: object
      required: [signers]
      properties:
        signers:
          type: array
          minItems: 1
          maxItems: 6
          items:
            $ref: "#/components/schemas/SignerInput"

    AddedSigner:
      type: object
      required: [signerId, name, email, signingOrder]
      properties:
        signerId:
          type: string
        name:
          type: string
        email:
          type: [string, "null"]
        signingOrder:
          type: integer
        advanced:
          $ref: "#/components/schemas/SignerPlacementEcho"

    AddSignersResponse:
      type: object
      required: [signers]
      properties:
        signers:
          type: array
          items:
            $ref: "#/components/schemas/AddedSigner"

    CreatedDocument:
      type: object
      required: [documentId, name, status]
      properties:
        documentId:
          type: string
          format: uuid
        name:
          type: string
        status:
          $ref: "#/components/schemas/DocumentStatus"

    DocumentSummary:
      type: object
      required: [documentId, name, status, createdAt, signers]
      properties:
        documentId:
          type: string
        name:
          type: string
        status:
          $ref: "#/components/schemas/DocumentStatus"
        createdAt:
          type: string
          format: date-time
        signers:
          type: object
          required: [total, signed]
          properties:
            total:
              type: integer
            signed:
              type: integer

    DocumentList:
      type: object
      required: [documents, limit, offset]
      properties:
        documents:
          type: array
          items:
            $ref: "#/components/schemas/DocumentSummary"
        limit:
          type: integer
        offset:
          type: integer

    DocumentSigner:
      type: object
      required: [signerId, name, email, phone, signingOrder, openedAt, signedAt, hasSigned, signUrl]
      properties:
        signerId:
          type: string
        name:
          type: string
        email:
          type: [string, "null"]
        phone:
          type: string
        signingOrder:
          type: integer
        openedAt:
          type: [string, "null"]
          format: date-time
        signedAt:
          type: [string, "null"]
          format: date-time
        hasSigned:
          type: boolean
        signUrl:
          type: [string, "null"]
          format: uri
          description: "`null` until the document has been sent."
        advanced:
          $ref: "#/components/schemas/SignerPlacementEcho"

    Document:
      type: object
      required: [documentId, name, status, createdAt, updatedAt, completedAt, downloadUrl, certificateUrl, signers]
      properties:
        documentId:
          type: string
        name:
          type: string
        status:
          $ref: "#/components/schemas/DocumentStatus"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        completedAt:
          type: [string, "null"]
          format: date-time
        downloadUrl:
          type: [string, "null"]
          format: uri
          description: Temporary presigned link to the signed PDF, set once `COMPLETED`.
        certificateUrl:
          type: [string, "null"]
          format: uri
          description: Certificate endpoint (Bearer auth), set once `COMPLETED`.
        signers:
          type: array
          items:
            $ref: "#/components/schemas/DocumentSigner"

    SentSigner:
      type: object
      required: [signerId, name, email, signingOrder, signUrl]
      properties:
        signerId:
          type: string
        name:
          type: string
        email:
          type: [string, "null"]
        signingOrder:
          type: integer
        signUrl:
          type: string
          format: uri

    SendDocumentResponse:
      type: object
      required: [documentId, status, creditsRemaining, signers]
      properties:
        documentId:
          type: string
        status:
          type: string
          const: SENT
        creditsRemaining:
          type: integer
        signers:
          type: array
          items:
            $ref: "#/components/schemas/SentSigner"

    WebhookEventType:
      type: string
      enum: [signer.signed, document.completed]

    WebhookSigner:
      type: object
      required: [signerId, name, email, signingOrder, signedAt, hasSigned]
      properties:
        signerId:
          type: string
        name:
          type: string
        email:
          type: [string, "null"]
        signingOrder:
          type: integer
        signedAt:
          type: [string, "null"]
          format: date-time
        hasSigned:
          type: boolean

    SignerSignedEvent:
      type: object
      required: [event, documentId, status, occurredAt, signer, signers]
      properties:
        event:
          type: string
          const: signer.signed
        documentId:
          type: string
        status:
          $ref: "#/components/schemas/DocumentStatus"
        occurredAt:
          type: string
          format: date-time
        signer:
          description: The signer who just signed.
          oneOf:
            - $ref: "#/components/schemas/WebhookSigner"
            - type: "null"
        signers:
          type: array
          items:
            $ref: "#/components/schemas/WebhookSigner"

    DocumentCompletedEvent:
      type: object
      required: [event, documentId, status, occurredAt, completedAt, certificateUrl, signers]
      properties:
        event:
          type: string
          const: document.completed
        documentId:
          type: string
        status:
          $ref: "#/components/schemas/DocumentStatus"
        occurredAt:
          type: string
          format: date-time
        completedAt:
          type: [string, "null"]
          format: date-time
        certificateUrl:
          type: string
          format: uri
        signers:
          type: array
          items:
            $ref: "#/components/schemas/WebhookSigner"
