openapi: 3.1.0
info:
  title: Formify API
  description: |-
    ## Overview
    
    The Formify API lets you send documents for electronic signature, track signing status, and retrieve signed documents. It is designed for integrating signing workflows into your application.
    
    ### Getting Access

    Before you can use the API, you need an OAuth 2.0 client. Formify creates this for you based on information about your company, contacts, and redirect URIs. See the **[Getting Access](/guides/getting-access/)** guide for setup instructions.
    
    Once your OAuth client has been created, you can use the [OAuth 2.0 Authorization Code flow](#tag/oauth) to obtain access tokens for API requests.
    
    ### Authentication
    
    All API requests require an OAuth 2.0 access token. To obtain one, your application redirects the user to Formify for authorization, receives an authorization code, and exchanges it for an access token. See the [OAuth 2.0 section](#tag/oauth) for the complete flow.

    ### Environments
    
    **API base URL**: `https://docs-api.formify.se/v1`  
    All document operations, file uploads, and OAuth token exchange happen here.
    
    **Authorization Server**: `https://app.formify.eu`  
    This is where users authorize your application and approve access (the OAuth consent).
    
    ### Rate Limiting
    
    The API uses token-based rate limiting to ensure fair usage:
    
    - **Headers**: Every response includes rate limit headers:
      - `X-RateLimit-Limit`: Maximum requests allowed
      - `X-RateLimit-Remaining`: Requests remaining in current window
      - `X-RateLimit-Reset`: The number of seconds until the rate limit resets

    If you exceed the limit, the API returns `429 Too Many Requests`. Wait until the reset time before making additional requests.
    
    ---
    
    ## Quick Start: Send a PDF for Signing
    
    Once you have OAuth credentials and a valid `access_token`, you can send your first document for signing in three steps:
    
    ### 1. Upload a PDF
    Upload a PDF file using the [Upload File](#tag/files/operation/uploadFile) endpoint:
    
    ```bash
    POST /files
    Content-Type: multipart/form-data
    ```
    
    You will receive a `fileId` in the response.
    
    ### 2. Create a Signature Request
    Send the document for signing using the [Create Document](#tag/docs/operation/createDocument) endpoint:

    
    ```bash
    POST /docs
    {
      "fileId": "your-file-id",
      "signeeDetails": [
        {
          "fullName": "John Doe",
          "emailAddress": "john@example.com",
          "signaturePlacement": "new_page"
        }
      ]
    }
    ```
    
    The response returns a documentId. The signee then receives an email with a link to sign the document.
    
    ### 3. Get Notified and Download
    Register a [webhook](#tag/webhooks/operation/registerWebhook) to receive a notification when the document is signed:
    
    ```bash
    POST /webhooks
    {
      "webhookUrl": "https://your-app.com/webhook",
      "eventTypes": ["document.completed"]
    }
    ```
    
    Then download the signed PDF using [Get Signed Document File](#tag/docs/operation/downloadSignedFile):
    
    ```bash
    GET /docs/{documentId}/signed-file
    ```
    
    For more detailed walkthroughs, see the **[Integration Flows](/guides/integration-flows/)** guide.
    
    ---
    
    ### Help
    
    - **[Getting Access](/guides/getting-access/)** - How to set up OAuth 2.0 credentials
    - **[Integration Flows](/guides/integration-flows/)** - Step-by-step guides for common integration scenarios
    - **[Production Checklist](/guides/production-checklist/)** - Essential steps before going live
    
    ### Links
    
    - [Formify API Changelog](/changelog) - Track API updates and changes
    - [Formify App](https://app.formify.eu) - Register OAuth applications and manage settings
    
    ---
    
  contact:
    email: api@formify.eu
  version: 1.0.0

  termsOfService: https://formify.eu/privacy/terms
externalDocs:
  description: Formify website
  url: https://formify.eu
servers:
  - url: https://docs-api.formify.se/v1
    description: "Main API server for all document related endpoints and OAuth 2.0 token exchange."
tags:
  - name: oauth
    description: |-
      ## OAuth 2.0 Authorization Code Grant Flow
      
      Formify uses OAuth 2.0 to allow third-party applications to access the API on behalf of users. This section provides a step-by-step guide to implementing the authorization flow.
      
      ### Prerequisites

      See the **[Getting Access](/guides/getting-access/)** guide for details, but in short you must:
      
      1. Obtain your `client_id` and `client_secret`.
      2. Register your application at [app.formify.eu](https://app.formify.eu)
      3. Configure your `redirect_uri` (must match exactly what you registered)
      
      ### Authorization Flow
      
      #### Step 1: Redirect User to Authorization URL
      
      Redirect the user to Formify's authorization endpoint:
      
      ```
      https://app.formify.eu/#/oauth?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=read,write&state=RANDOM_STATE_STRING
      ```
      
      **Parameters:**
      - `client_id` (required): Your application's client ID
      - `redirect_uri` (required): Where to redirect after authorization (must be registered)
      - `response_type` (required): Always use `code` for authorization code flow
      - `scope` (required): Comma-separated list of permissions (`read`, `write`, or `read,write`)
      - `state` (recommended): Random string to prevent CSRF attacks
      
      #### Step 2: User Authorizes Your Application
      
      The user sees a consent screen and approves your application's access to their Formify account.
      
      #### Step 3: Receive Authorization Code
      
      After approval, Formify redirects back to your `redirect_uri` with an authorization code:
      
      ```
      https://your-app.com/callback?code=AUTH_CODE_HERE&state=RANDOM_STATE_STRING
      ```
      
      **Important:** Verify that the `state` parameter matches what you sent in Step 1.
      
      #### Step 4: Exchange Code for Access Token
      
      Make a POST request to the token endpoint to exchange the authorization code for an access token:
      
      ```bash
      POST https://docs-api.formify.se/v1/oauth/token
      Content-Type: application/x-www-form-urlencoded
      
      grant_type=authorization_code
      &code=AUTH_CODE_HERE
      &redirect_uri=YOUR_REDIRECT_URI
      &client_id=YOUR_CLIENT_ID
      &client_secret=YOUR_CLIENT_SECRET
      ```
      
      **Response:**
      ```json
      {
        "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
        "token_type": "Bearer",
        "expires_in": 3600,
        "refresh_token": "def502004a1b2c3d...",
        "scope": "read,write"
      }
      ```
      
      #### Step 5: Use Access Token
      
      Include the access token in the `Authorization` header for all API requests:
      
      ```bash
      GET https://docs-api.formify.se/v1/docs
      Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
      ```
      
      ### Token Refresh
      
      Access tokens expire after the time specified in `expires_in` (typically 1 hour). Use the refresh token to obtain a new access token without requiring user interaction:
      
      ```bash
      POST https://docs-api.formify.se/v1/oauth/token
      Content-Type: application/x-www-form-urlencoded
      
      grant_type=refresh_token
      &refresh_token=YOUR_REFRESH_TOKEN
      &client_id=YOUR_CLIENT_ID
      &client_secret=YOUR_CLIENT_SECRET
      ```
      
      **Response:** Same structure as the initial token response, including a new `refresh_token`.
      
      ### Scope
      
      - **`read`**: Allows reading documents, files, user info, and account settings
      - **`write`**: Allows creating/updating documents, uploading files, and modifying settings
      
      Request only the scopes your application needs. Users see which permissions you're requesting during authorization.
      
      ### Common Errors
      
      | Error | Description | Solution |
      |-------|-------------|----------|
      | `invalid_grant` | Authorization code expired or already used | Codes expire after 10 minutes. Start the flow again. |
      | `invalid_client` | Invalid client_id or client_secret | Verify your credentials in app.formify.eu |
      | `redirect_uri_mismatch` | redirect_uri doesn't match registered URI | Must match exactly (including trailing slash) |
      | `invalid_scope` | Requested scope not allowed | Use only `read` and/or `write` |
      | `access_denied` | User denied authorization | User must approve for your app to work |
      
      ### Security Best Practices
      
      - **Never expose** `client_secret` in client-side code
      - **Always use HTTPS** for redirect URIs
      - **Validate state parameter** to prevent CSRF attacks
      - **Store tokens securely** (encrypted database, secure session storage)
      - **Refresh tokens proactively** before they expire to avoid service interruption
  - name: files
    description: Upload files to use for signature requests
  - name: docs
    description: Handle and create document signature requests
  - name: user
    description: User info and operations
  - name: account
    description: Account info and operations
  - name: webhooks
    description: Webhooks operations
  - name: templates
    description: Template operations

x-tagGroups:
  - name: Authentication
    tags:
      - oauth
  - name: Documents
    tags:
      - files
      - docs
      - templates
  - name: Account & User
    tags:
      - user
      - account
  - name: Webhooks
    tags:
      - webhooks

security:
  - OAuth2: [] # Default security for all endpoints. All API requests require OAuth 2.0 authentication.

components:
  securitySchemes:
    OAuth2:
      type: oauth2
      description: "OAuth2 Authentication. Allows third-party systems to access the API on behalf of the user and their account after authorization."
      flows:
        authorizationCode:
          authorizationUrl: https://app.formify.eu/#/oauth  # Frontend for user authorization
          tokenUrl: https://docs-api.formify.se/v1/oauth/token  # Backend for token exchange
          scopes:
            read: "Allows reading documents and their metadata"
            write: "Allows modifying documents and sending signature requests"
  schemas:
    SignatureField:
      type: object
      description: |-
        Represents a signee who will sign the document. Each SignatureField defines one person's signature requirements, including their contact information, signature type, placement, and signing order.
        
        **Usage:**
        - Include in the `signeeDetails` array when creating a document via `POST /docs`
        - At least one SignatureField is required per document
        - Multiple SignatureFields enable multi-party signing workflows
        
        **Key Concepts:**
        - **Contact Methods**: Signees can be notified via email, SMS, or WhatsApp (at least one method is required)
        - **Signature Types**: Choose between digital ink, BankID identification, ID scan + ink signature, or ID scan + biometric face liveness verification
        - **Placement**: Signatures can be placed on existing pages or on a new signature page, except `digital_ink_id_scan`, which must use `existing`
        - **Signing Order**: Control the sequence in which signees can sign (sequential or simultaneous)
        
        **Special Rules:**
        - When `signaturePlacement` is `existing`, `signatureBox.page`, `signatureBox.x`, and `signatureBox.y` are required.
        - When `signatureType` is `digital_ink_id_scan`, `signaturePlacement` must be `existing` and both `signatureBox` and `idScanBox` are required.
        
        **Common Scenarios:**
        - Single signee: One SignatureField with default settings
        - Multiple simultaneous signees: Multiple SignatureFields with same `signingOrder` value
        - Sequential signing: Multiple SignatureFields with incrementing `signingOrder` values (1, 2, 3...)
      example:
        fullName: "Jane Smith"
        emailAddress: "jane.smith@example.com"
        phoneNumber: "+46701234567"
        phoneNumberDeliveryMethod: "sms"
        signatureType: "digital_ink_id_scan"
        signaturePlacement: "existing"
        signatureBox:
          page: 0
          x: 72
          y: 560
          scale: 1.0
        idScanBox:
          page: 0
          x: 320
          y: 480
          scale: 1.0
        signingOrder: 1
      properties:
        fullName:
          type: string
          description: "Full name of the signee"
        emailAddress:
          type: string
          format: email
          description: "Email address of the signee"
        phoneNumber:
          type: string
          description: |-
            The signee's phone number, formatted as an international number with a leading `+` followed by 9 to 15 digits.
            
            ### Format
            - Must start with a `+`, followed by the country code and phone number.
            - Avoid spaces, dashes, or other separators.

            Requires either the SMS Invitation add-on or WhatsApp Invitation add-on enabled for the account.
          pattern: "^\\+[0-9]{9,15}$"
        phoneNumberDeliveryMethod:
          type: string
          enum:
            - sms
            - whatsapp
          default: sms
          description: |-
            Method used to deliver messages to the provided `phoneNumber`.

            - `sms`: Send an SMS mobile phone message (default, requires SMS Invitation add-on enabled for the account)
            - `whatsapp`: Send via WhatsApp (requires WhatsApp Invitation add-on enabled for the account)

            Only applicable when `phoneNumber` is set.
        signatureType:
          type: string
          enum:
            - digital_ink
            - bankid_identification
            - digital_ink_id_scan
            - face_liveness
          default: digital_ink
          description: |-
            Type of signature / identity verification method used by the signee.

            - `digital_ink`: The signee draws their signature using a mouse, touchpad, or finger.
            - `bankid_identification`: The signee signs using Swedish BankID.
            - `digital_ink_id_scan`: The signee verifies identity with an ID scan and then signs with digital ink.
            - `face_liveness`: The signee verifies identity using an ID scan together with a video selfie biometric liveness check.
        signaturePlacement:
          type: string
          enum:
            - existing
            - new_page
          default: existing
          description: |-
            Option to place the signature box on an existing page (`existing`, default) or place the field on a new signature page (A4 portrait format) at the end of the document (`new_page`).

            `digital_ink_id_scan` does not support `new_page` and must use `existing`, because both the signature field and the ID scan placeholder must be placed inside the document.
        signingOrder:
          type: integer
          minimum: 1
          default: 1
          description: |-
            Defines the order in which this signee can sign the document. Only applicable when `enableSigningOrder` is set to `true` in the document request.
            
            - **Default**: `1` (all signees can sign simultaneously)
            - **Sequential Signing**: Set different values (1, 2, 3, etc.) to enforce a signing sequence
            - **Simultaneous Signing**: Multiple signees with the same value can sign at the same time
            
            Signees will receive their invitation (email/SMS/WhatsApp) only when it's their turn to sign based on this order.
        signatureBox:
          type: object
          description: |-
            Placement of the signature field when `signaturePlacement` is `existing`.

            Base size before scaling:
            - Width: 219 points
            - Height: 58 points
          properties:
            page:
              type: integer
              description: "Page number where the signature box should be placed. The first page has index 0."
            x:
              type: integer
              minimum: 0
              description: "X-coordinate for the signature box placement (minimum value 0)"
            y:
              type: integer
              minimum: 0
              description: "Y-coordinate for the signature box placement (minimum value 0)"
            scale:
              type: number
              format: float
              minimum: 0.25
              maximum: 1.5
              default: 1.0
              description: "Scales the entire signature field. Applies only when `signaturePlacement` is `existing`."
            formFieldId:
              type: string
              description: |-
                ⚠️ formFieldId is currently not available in production and is under development.

                ID of the form field where the signature should be placed (only used if coordinates are not set).
        idScanBox:
          type: object
          description: |-
            Placement of the scanned ID placeholder used when `signatureType` is `digital_ink_id_scan`.

            This property is required for `digital_ink_id_scan` and is not used for other signature types.

            Base size before scaling:
            - Width: 218 points
            - Height: 138 points
          properties:
            page:
              type: integer
              description: "Page number where the ID scan placeholder should be placed. The first page has index 0."
            x:
              type: integer
              minimum: 0
              description: "X-coordinate for the ID scan placeholder placement (minimum value 0)"
            y:
              type: integer
              minimum: 0
              description: "Y-coordinate for the ID scan placeholder placement (minimum value 0)"
            scale:
              type: number
              format: float
              minimum: 0.25
              maximum: 1.5
              default: 1.0
              description: "Scales the entire ID scan placeholder. Applies only when `signatureType` is `digital_ink_id_scan`."
    AiAssistantResponse:
      type: object
      nullable: true
      description: >
        AI Assistant configuration for this document. Only included in the response
        if the document was created with AI Assistant enabled.
      properties:
        enabled:
          type: boolean
          description: Whether AI Assistant is enabled for this document.
          example: true
        textToSpeech:
          type: boolean
          description: Whether text-to-speech is enabled for the AI Assistant.
          example: false
        language:
          type: string
          description: The language configured for the AI Assistant.
          example: "en"
          enum: [ar, bg, zh, hr, cs, da, nl, en, fil, fi, fr, de, el, hi, id, it, ja, ko, ms, pl, pt, ro, ru, es, sv, ta, tr, uk]
    FormField:
      type: object
      description: |-
        Represents an interactive form field embedded in a PDF document. FormFields are extracted from PDF forms and can be read or updated via the API.
        
        **Usage:**
        - Retrieve form fields from uploaded files: `GET /files/{fileId}/fields`
        - Retrieve form fields from documents: `GET /docs/{documentId}/fields`
        - Get field values from completed documents: `GET /docs/{documentId}/field-values`
        - Update field values: `POST /docs/{documentId}/field-values`
        
        **Field Types:**
        - **textField**: Single-line or multi-line text input
        - **checkbox**: Boolean selection (checked/unchecked)
        - **radioButton**: Single selection from a group of options
        - **combobox**: Dropdown with editable text input
        - **listbox**: List selection (single or multiple)
        
        **Common Use Cases:**
        - Pre-fill contract templates with customer data
        - Collect structured information from signees
        - Create dynamic forms with conditional logic
        - Extract data from completed forms for processing
        
        **Key Properties:**
        - `name`: Unique identifier for the field (use this when updating values)
        - `required`: Whether the field must be filled before signing
        - `editable`: Whether the field can be updated via API
        - `readOnly`: Whether signees can modify the field
      example:
        fieldId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        name: "customer_email"
        label: "Email Address"
        type: "textField"
        value: null
        required: true
        readOnly: false
        editable: true
        page: 0
      properties:
        fieldId:
          type: string
          format: uuid
          description: "Unique identifier for the field."
        name:
          type: string
          description: "The name of the form field, used as the key for API interactions."
        label:
          type: string
          description: "The display label of the form field."
        type:
          type: string
          enum:
            - checkbox
            - combobox
            - listbox
            - radioButton
            - textField
          description: "The type of the form field."
        value:
          type: string
          nullable: true
          description: "The initial value of the form field, if any."
        defaultValues:
          type: array
          items:
            type: string
          nullable: true
          description: "List of default values for the form field."
        options:
          type: array
          items:
            type: object
            properties:
              label:
                type: string
                description: "The display label of the option."
              value:
                type: string
                description: "The value associated with the option."
          nullable: true
          description: "List of available options for dropdown or choice fields."
        required:
          type: boolean
          description: "Indicates if the field is mandatory for submission."
        readOnly:
          type: boolean
          description: "Indicates if the field is read-only and cannot be modified."
        editable:
          type: boolean
          description: "Indicates if the field is editable through the `POST /docs/{documentId}/field-values` endpoint."
        multiLine:
          type: boolean
          nullable: true
          description: "Indicates if the field supports multi-line text."
        comb:
          type: boolean
          nullable: true
          description: "Indicates if the field uses comb formatting for text input."
        page:
          type: integer
          description: "The page index (zero-based) where the field is located."
        boundingBox:
          type: object
          nullable: true
          properties:
            x:
              type: number
              description: "The X coordinate of the field's top-left corner."
            y:
              type: number
              description: "The Y coordinate of the field's top-left corner."
            width:
              type: number
              description: "The width of the field in points."
            height:
              type: number
              description: "The height of the field in points."
          description: "The bounding box of the field on the page."
    FormFieldValue:
      type: object
      properties:
        name:
          type: string
          description: "The name of the form field to update."
        value:
          oneOf:
            - type: string
            - type: array
              items:
                type: string
          description: |-
            The new value(s) for the form field.
            - **For single-value fields (e.g., text fields, radio buttons):** Provide a **single string**.
            - **For multi-value fields (e.g., checkboxes, listboxes):** Provide an **array of strings**.
          example: "John Doe"
      example:
        name: "email"
        value: "test@example.com"
    AiAssistant:
      type: object
      description: |-
        Configuration for the optional AI Assistant shown to signees while reviewing and signing a document.
        
        **Usage:**
        - Set `enabled` to `true` to activate the assistant for the document.
        - Set `textToSpeech` to `true` if the assistant should be able to read its responses aloud.
        - Use `language` to control the assistant language.
        
        **Defaults:**
        - `enabled`: `false` 
        - `textToSpeech`: `false` 
        - `language`: `en` 
        
        **Processing:**
        When the AI Assistant is enabled, the document will be created with `status: "processing"` and `documentUrl` will be `null`. The document will be finalized asynchronously, and invitations to signees will be sent once processing is complete.
        
        > **Availability:** The AI Assistant feature may require account enablement. Please contact Formify to request access.
      example:
        enabled: true
        textToSpeech: false
        language: en
      properties:
        enabled:
          type: boolean
          default: false
          description: Enables the AI Assistant for the document.
        textToSpeech:
          type: boolean
          default: false
          description: Enables text-to-speech so the assistant can read its responses aloud.
        language:
          type: string
          enum:
            - ar
            - bg
            - zh
            - hr
            - cs
            - da
            - nl
            - en
            - fil
            - fi
            - fr
            - de
            - el
            - hi
            - id
            - it
            - ja
            - ko
            - ms
            - pl
            - pt
            - ro
            - ru
            - es
            - sv
            - ta
            - tr
            - uk
          default: en
          description: Language used by the AI Assistant. Supports 28 languages.
    OauthTokenResponse:
      type: object
      description: |-
        OAuth 2.0 token response returned after successful authorization. This object contains the access token (JWT) and refresh token needed to authenticate API requests.
        
        **Usage:**
        - Include `access_token` in the `Authorization: Bearer {token}` header for all API requests
        - Use `refresh_token` to obtain a new access token before expiration
        - Store tokens securely (encrypted database or secure session storage)
      properties:
        access_token:
          type: string
          description: |-
            The JWT (JSON Web Token) access token used for authenticating API requests. This token must be included in the `Authorization` header as `Bearer {access_token}` for all protected endpoints.
            
            **Format:** JWT with three parts separated by dots (header.payload.signature)
            **Lifetime:** Specified in `expires_in` field (typically 3600 seconds / 1 hour)
          example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
        expires_in:
          type: integer
          description: |-
            The lifetime of the access token in seconds. After this time, the token expires and can no longer be used.
            
            **Typical value:** 3600 (1 hour)
            **Best practice:** Refresh the token proactively before expiration to avoid service interruption
          example: 3600
        token_type:
          type: string
          description: |-
            The type of token issued. Always `Bearer` for OAuth 2.0 access tokens.
            
            **Usage:** Include as `Authorization: Bearer {access_token}` in request headers
          example: "Bearer"
        refresh_token:
          type: string
          description: |-
            A long-lived token used to obtain a new access token when the current one expires. Unlike access tokens, refresh tokens do not expire automatically but can be revoked.
            
            **Usage:** Send to `/oauth/token` with `grant_type=refresh_token` to get a new access token
            **Security:** Store securely and never expose in client-side code or logs
          example: "def502004a1b2c3d4e5f6789abcdef0123456789"
        scope:
          type: string
          description: |-
            Comma-separated list of permissions granted to this token. Determines which API operations the token can access.
            
            **Possible values:**
            - `read`: Read-only access to documents, files, and account data
            - `write`: Full access including creating/updating documents and files
            - `read,write`: Both read and write permissions
          example: "read,write"
    ErrorResponse401:
      type: object
      properties:
        success:
          type: boolean
          example: false
        message:
          type: string
          example: "Unauthorized"
        code:
          type: integer
          example: 401
        language:
          type: string
          example: "en"
    ErrorResponse403:
      type: object
      properties:
        success:
          type: boolean
          example: false
        message:
          type: string
          example: "Forbidden"
        code:
          type: integer
          example: 403
        language:
          type: string
          example: "en"
    RateLimitHeaders:
      type: object
      properties:
        X-RateLimit-Limit:
          type: integer
          example: 50
          description: "The maximum number of requests allowed within a replenishment period."
        X-RateLimit-Remaining:
          type: integer
          example: 46
          description: "The number of requests remaining in the current replenishment period."
        X-RateLimit-Reset:
          type: integer
          example: 2
          description: "The number of seconds until the rate limit resets."

    ErrorResponse404:
      type: object
      properties:
        success:
          type: boolean
          example: false
        message:
          type: string
          example: "Not Found"
        code:
          type: integer
          example: 404
        language:
          type: string
          example: "en"

    ErrorResponse500:
      type: object
      properties:
        success:
          type: boolean
          example: false
        message:
          type: string
          example: "Internal Server Error"
        code:
          type: integer
          example: 500
        language:
          type: string
          example: "en"
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        message:
          type: string
          example: "Invalid request parameters"
        code:
          type: integer
          example: 400
        language:
          type: string
          example: "en"
    DocumentHistoryEvent:
      type: object
      description: |-
        Represents a single event in a document's lifecycle history. DocumentHistoryEvents track all significant actions that occur during a document's journey from creation to completion.
        
        **Usage:**
        - Retrieved via `GET /docs/{documentId}/history`
        - Provides audit trail of document activities
        - Includes information about who performed each action
        
        **Event Types:**
        - `document_created`: Document was created and sent for signature
        - `document_opened`: Document was opened by a signee
        - `document_signed`: Document was signed by a signee
        - `document_completed`: All signatures collected, document sealed
        - `document_revoked`: Document signing process was cancelled
        - `document_deleted`: Document was permanently deleted
        
        **Use Cases:**
        - Audit logging and compliance tracking
        - Monitoring document progress
        - Troubleshooting signing issues
        - Generating activity reports
      example:
        eventType: "document_signed"
        eventDate: "2024-03-17T14:23:55Z"
        name: "Jane Smith"
        email: "jane.smith@example.com"
        phoneNumber: "+46701234567"
      properties:
        eventType:
          type: string
          enum:
            - document_opened
            - document_signed
            - document_completed
            - document_revoked
            - document_deleted
            - document_created
          description: "The type of the document-related event that triggered the webhook."
        eventDate:
          type: string
          format: date-time
          description: "Date and time when the event occurred"
        name:
          type: string
          description: "Name of the person involved in the event, if applicable"
        email:
          type: string
          format: email
          description: "Email address of the person involved in the event, if applicable"
        phoneNumber:
          type: string
          description: "Phone number of the person involved in the event, if applicable"
    DocumentWebhookEvent:
      type: object
      description: |-
        The payload structure sent to your webhook endpoint when document events occur. This is the complete object your webhook receiver will receive in the request body.
        
        **Usage:**
        - Received as POST request body at your registered webhook URL
        - Content-Type: `application/json`
        - Must respond with 2xx status code to acknowledge receipt
        
        **Structure:**
        - `event`: Contains metadata about the event (eventId, type, date)
        - `data`: Contains identifiers for the affected resources (documentId, accountId, userId, signeeId)
        
        **Important Notes:**
        - Use `event.eventId` for idempotency (prevent duplicate processing)
        - The `data.signeeId` field is only present for `document.signed` events
        - Events may arrive out of order - use timestamps for sequencing
        - Store eventId to detect and ignore duplicate deliveries
        
        **Common Integration Pattern:**
        1. Receive webhook POST request
        2. Validate eventId hasn't been processed before
        3. Store eventId in database
        4. Process the event based on `event.type`
        5. Respond with 200 OK
      example:
        event:
          eventId: "123e4567-e89b-12d3-a456-426614174000"
          type: "document.completed"
          date: "2024-03-17T14:23:55Z"
        data:
          documentId: "789e4567-e89b-12d3-a456-426614174111"
          accountId: "456e4567-e89b-12d3-a456-426614174222"
          userId: "123e4567-e89b-12d3-a456-426614174333"
      properties:
        event:
          type: object
          properties:
            eventId:
              type: string
              format: uuid
              description: Unique identifier for this webhook event.
            type:
              type: string
              enum: [document.created, document.opened, document.signed, document.completed, document.revoked, document.deleted]
              description: The type of event that triggered the webhook.
            date:
              type: string
              format: date-time
              description: ISO 8601 timestamp of when the event occurred.
        data:
          type: object
          description: Event-specific data.
          properties:
            documentId:
              type: string
              format: uuid
            accountId:
              type: string
              format: uuid
            userId:
              type: string
              format: uuid
            signeeId:
              type: string
              format: uuid
              nullable: true
              description: Only present for `document.signed` events.

paths:
  /#/oauth:
    get:
      tags:
        - oauth
      summary: "OAuth authorize URL (browser redirect, not an API request)"
      description: |
        > **This is not a REST endpoint.** Redirect the user's browser to this URL — do not call it as a server-to-server API request.

        ### Step 1: Requesting authorization

        First, redirect the customer's web browser to the authorize endpoint in the Formify app. The customer will be asked to authorize your app to access their Formify account.

        **Authorize URL:**
        ```
        GET https://app.formify.eu/#/oauth?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&scope=read,write&state={state}
        ```

        **Parameters:**

        | Parameter       | Required | Description                                                                                           |
        |-----------------|----------|-------------------------------------------------------------------------------------------------------|
        | `client_id`     | Yes      | The client ID provided to you by Formify when you register your app.                                  |
        | `redirect_uri`  | Yes      | The callback URL you provided when you registered your app. This should be URL-encoded.               |
        | `response_type` | Yes      | Must be `code`.                                                                                       |
        | `scope`         | Yes      | Requested access scope. One of `read`, `write`, or `read,write`.                                      |
        | `state`         | Recommended | Strongly recommended for CSRF protection. Send a random value and verify that the same value is returned to your callback.  |

        **Example of URL-encoded redirect_uri:**
        https%3A%2F%2Fwww.your-domain.com%2Foauth%2Fcallback

        ### Step 2: Customer Authorizes the App

        The customer will see a confirmation dialog. If they authorize, they will be redirected to the callback URL with a code parameter:

        ```
        GET https://your-callback-url.com?code={authorization_code}&state={state}
        ```

        ### Step 3: Exchange Authorization Code for Access Token

        Make a `POST` request to `/oauth/token` through the API to exchange the authorization code for an access token and refresh token. The refresh token is normally valid for 60 days.

        **Request:**

        ```
        POST https://docs-api.formify.se/v1/oauth/token
        Content-Type: application/x-www-form-urlencoded

        grant_type=authorization_code&code={authorization_code}&client_id={client_id}&client_secret={client_secret}&redirect_uri={redirect_uri}
        ```

        **Response:**

        ```
        {
          "access_token": "{access_token}",
          "expires_in": 3600,
          "refresh_token": "{refresh_token}",
          "token_type": "Bearer",
          "scope": "read,write"
        }
        ```

      x-codeSamples:
        - lang: JavaScript
          label: Browser redirect
          source: |-
            const params = new URLSearchParams({
              client_id: "YOUR_CLIENT_ID",
              redirect_uri: "https://your-app.com/callback",
              response_type: "code",
              scope: "read,write",
              state: "RANDOM_STATE_STRING"
            });

            window.location.href = `https://app.formify.eu/#/oauth?${params.toString()}`;
        - lang: Python
          label: Backend redirect (Flask)
          source: |-
            from urllib.parse import urlencode
            from flask import redirect

            params = urlencode({
                "client_id": "YOUR_CLIENT_ID",
                "redirect_uri": "https://your-app.com/callback",
                "response_type": "code",
                "scope": "read,write",
                "state": "RANDOM_STATE_STRING",
            })

            return redirect(f"https://app.formify.eu/#/oauth?{params}")
        - lang: curl
          label: Authorize URL
          source: |-
            # Open this URL in the user's browser — do not call it from your server
            https://app.formify.eu/#/oauth?client_id=YOUR_CLIENT_ID&redirect_uri=https%3A%2F%2Fyour-app.com%2Fcallback&response_type=code&scope=read%2Cwrite&state=RANDOM_STATE_STRING
      parameters:
        - name: client_id
          in: query
          required: true
          schema:
            type: string
          description: "The client ID provided to you when you registered your app."
        - name: state
          in: query
          schema:
            type: string
          description: "Strongly recommended for CSRF protection. Send a random value and verify that the same value is returned to your callback."
        - name: redirect_uri
          in: query
          required: true
          schema:
            type: string
          description: "The callback URL you registered for your app."
        - name: response_type
          in: query
          required: true
          schema:
            type: string
            enum: [code]
          description: "Must be `code`."
        - name: scope
          in: query
          required: true
          schema:
            type: string
            enum: [read, write, "read,write"]
          description: "Requested access scope. Use `read,write` for full access."
        - in: query
          name: errorLanguage
          schema:
            type: string
            enum: [en, sv, es]
          description: "Language for the error message"
      responses:
        '302':
          description: "Redirection to the provided `redirect_uri` with the authorization code as a query parameter."
      servers:
        - url: https://app.formify.eu
          description: Authorization server for handling OAuth 2.0 authorization flows.
      security: [] # No authentication required for this endpoint

  /oauth/token:
    post:
      tags:
        - oauth
      summary: "Generate OAuth 2.0 access token using authorization code or refresh token"
      description: |-
        This endpoint is used to exchange an authorization code or refresh token for an access token. 
        
        ### Notes:
        - The **Authorization Code Grant Flow** requires a `grant_type` of `authorization_code` and a valid `code` obtained from the authorization URL.
        - The **Refresh Token Grant Flow** requires a `grant_type` of `refresh_token` and a valid `refresh_token`.
        - Include your `client_id` and `client_secret` in the request body for authentication.
      x-codeSamples:
        - lang: curl
          source: |-
            curl -X POST "https://docs-api.formify.se/v1/oauth/token" \
              -H "Content-Type: application/x-www-form-urlencoded" \
              -d "grant_type=authorization_code" \
              -d "code=AUTH_CODE_HERE" \
              -d "redirect_uri=https://your-app.com/callback" \
              -d "client_id=YOUR_CLIENT_ID" \
              -d "client_secret=YOUR_CLIENT_SECRET"
        - lang: JavaScript
          source: |-
            const body = new URLSearchParams({
              grant_type: "authorization_code",
              code: "AUTH_CODE_HERE",
              redirect_uri: "https://your-app.com/callback",
              client_id: "YOUR_CLIENT_ID",
              client_secret: "YOUR_CLIENT_SECRET"
            });

            const response = await fetch("https://docs-api.formify.se/v1/oauth/token", {
              method: "POST",
              headers: {
                "Content-Type": "application/x-www-form-urlencoded"
              },
              body
            });

            const data = await response.json();
            console.log(data);
        - lang: Python
          source: |-
            import requests

            url = "https://docs-api.formify.se/v1/oauth/token"
            data = {
                "grant_type": "authorization_code",
                "code": "AUTH_CODE_HERE",
                "redirect_uri": "https://your-app.com/callback",
                "client_id": "YOUR_CLIENT_ID",
                "client_secret": "YOUR_CLIENT_SECRET",
            }

            response = requests.post(url, data=data)
            print(response.json())
      operationId: generateOauthAccessToken
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              properties:
                grant_type:
                  type: string
                  enum:
                    - authorization_code
                    - refresh_token
                  description: "The type of grant being used."
                client_id:
                  type: string
                  description: "OAuth client ID. Required for both grant types."
                client_secret:
                  type: string
                  description: "OAuth client secret. Required for both grant types."
                code:
                  type: string
                  description: "The authorization code issued to the client. Required when grant_type=authorization_code."
                redirect_uri:
                  type: string
                  description: "The redirect URI used in the authorization request. Required when grant_type=authorization_code."
                refresh_token:
                  type: string
                  description: "The refresh token issued to the client. Required when grant_type=refresh_token."
                scope:
                  type: string
                  description: "Comma-separated list of scopes for the access token."
                  example: "read,write"
                error_language:
                  type: string
                  enum: [en, sv, es]
                  description: "Language for the error message. Note: this field uses snake_case (`error_language`) following OAuth 2.0 form-encoded conventions, unlike `errorLanguage` used on other endpoints."
      responses:
        '200':
          description: "Token generated successfully"
          content:
            application/json:
              schema:
                type: object
                properties:
                  access_token:
                    type: string
                    description: "The access token."
                  expires_in:
                    type: integer
                    description: "The number of seconds until the access token expires."
                  refresh_token:
                    type: string
                    description: "The refresh token."
                  token_type:
                    type: string
                    description: "The type of token."
                  scope:
                    type: string
                    description: "The scope of the access token."
        '400':
          description: "Missing or invalid request"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: "Unauthorized"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '500':
          description: "Internal server error"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'
      security: [] # No authentication required for this endpoint

  /oauth/revoke:
    post:
      tags:
        - oauth
      summary: "Revoke OAuth 2.0 token and its associated grant"
      description: |-
        This endpoint is used to revoke an OAuth 2.0 token, which invalidates its associated grant. 
        
        ### Notes:
        - You can revoke either using an `access_token` or a `refresh_token`.

        ### Example Usage:
        #### Request Body:
        ```json
        {
          "token": "your_refresh_token_here",
          "token_type_hint": "refresh_token"
        }
        ```
        
        #### Response:
        If the token is successfully revoked:
        - HTTP Status: `200 OK` (no body content required).

        If the token is invalid or has already been revoked:
        - HTTP Status: `400 Bad Request`.

      x-codeSamples:
        - lang: curl
          source: |-
            curl -X POST "https://docs-api.formify.se/v1/oauth/revoke" \
              -H "Content-Type: application/x-www-form-urlencoded" \
              -d "token=your_refresh_token_here" \
              -d "token_type_hint=refresh_token"
        - lang: JavaScript
          source: |-
            const response = await fetch("https://docs-api.formify.se/v1/oauth/revoke", {
              method: "POST",
              headers: {
                "Content-Type": "application/x-www-form-urlencoded"
              },
              body: new URLSearchParams({
                token: "your_refresh_token_here",
                token_type_hint: "refresh_token"
              })
            });

            console.log(response.status); // 200
        - lang: Python
          source: |-
            import requests

            url = "https://docs-api.formify.se/v1/oauth/revoke"
            data = {
                "token": "your_refresh_token_here",
                "token_type_hint": "refresh_token",
            }

            response = requests.post(url, data=data)
            print(response.status_code)  # 200
      operationId: revokeToken
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              properties:
                token:
                  type: string
                  description: "The token to be revoked (either `access_token` or `refresh_token`)."
                token_type_hint:
                  type: string
                  enum:
                    - access_token
                    - refresh_token
                  description: "Hint about the type of token being revoked."
      responses:
        '200':
          description: "Token successfully revoked."
        '400':
          description: "Invalid or already revoked token."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: "Unauthorized - Invalid API keys or basic authentication."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '500':
          description: "Internal server error."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'
      security: [] # No authentication required for token revocation

  /files:
    post:
      tags:
        - files
      summary: "Upload a PDF file for signing"
      description: |-
        Uploads a PDF file to Formify, making it available for creating document signature requests via the `POST /docs` endpoint.

        ### Requirements
        - **File Format**: Only PDF files are supported.
        - **File Size**: Maximum allowed file size is **50 MB**.
        - **Unlocked PDFs Only**: The file must not be password-protected or locked in any way. Locked files cannot be processed.
        - **No Pre-Signed Documents**: The PDF must not contain digital signatures from other services, as these will be removed, invalidating the document's signature integrity.
        - **Signature Field Handling**: If the uploaded document contains existing signature fields (interactive PDF form fields of type `signature`), these will be removed automatically. New signature fields, fully compatible with the Formify signing process, must be added to the document instead. This ensures that all signature fields function seamlessly within the Formify platform.
        By ensuring these requirements are met, you can use the uploaded file to create a document signature request seamlessly.
      operationId: uploadFile
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: "The PDF document to be uploaded. Max file size: 50 MB."
                errorLanguage:
                  type: string
                  enum: [en, sv, es]
                  description: "Language for the error message"
        required: true
      responses:
        '201':
          description: "File uploaded successfully"
          content:
            application/json:
              schema:
                type: object
                properties:
                  fileId:
                    type: string
                    format: uuid
                    description: "The unique identifier of the uploaded file"
        '400':
          description: "Invalid request parameters or issues with the file"
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  message:
                    type: string
                    example: "File exceeds the maximum allowed size of 50 MB." 
                  code:
                    type: integer
                    example: 400
        '401':
          description: "Unauthorized - Invalid API keys or basic authentication"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '403':
          description: "Forbidden - Access to the file is denied"
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  message:
                    type: string
                    example: "Unauthorized access to the file."
                  code:
                    type: integer
                    example: 403
        '500':
          description: "Internal server error"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'

  /files/merge:
    post:
      tags:
        - files
      summary: "Merge multiple PDF files into a single document"
      description: |-
        Merges two or more uploaded PDF files into a single file. The resulting file will be assigned a new `fileId`.

        ### Requirements
        - **File Format**: Only pre-uploaded PDF files are supported for merging.
        - **Order of Merge**: The files will be merged in the order of the provided `fileIds`.
        - **File Name**: A name for the resulting merged file must be provided.
        - **Max File Size**: Ensure the combined file size does not exceed system limits (**50 MB**).
        - **2-8 Files**: A minimum of 2 files and a maximum of 8 `fileIds` are required (duplicate `fileIds` are not allowed.).

        This endpoint is particularly useful for workflows where multiple PDF files need to be combined before sending them for signatures or further processing.
      operationId: mergeFiles
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                fileIds:
                  type: array
                  items:
                    type: string
                    format: uuid
                    description: "Unique identifier of a file to be merged."
                  description: "List of File IDs to merge. Must contain between 2 and 8 unique File IDs."
                  example:
                    - "123e4567-e89b-12d3-a456-426614174000"
                    - "123e4567-e89b-12d3-a456-426614174001"
                name:
                  type: string
                  description: "Name for the resulting merged file."
                  example: "Merged Document"
      responses:
        '201':
          description: "Files merged successfully."
          content:
            application/json:
              schema:
                type: object
                properties:
                  fileId:
                    type: string
                    format: uuid
                    description: "The unique identifier of the merged file."
                    example: "123e4567-e89b-12d3-a456-426614174002"
        '400':
          description: "Invalid request parameters or issues with the files."
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  message:
                    type: string
                    example: "Invalid request: Must provide between 2 and 8 unique File IDs."
                  code:
                    type: integer
                    example: 400
        '401':
          description: "Unauthorized - Invalid API keys or basic authentication."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '403':
          description: "Forbidden - Access to one or more files is denied."
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  message:
                    type: string
                    example: "Unauthorized access to one or more files."
                  code:
                    type: integer
                    example: 403
        '500':
          description: "Internal server error."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'
    
  /files/{fileId}:
    delete:
      tags:
        - files
      summary: "Delete an uploaded file"
      operationId: deleteFileById
      parameters:
        - name: fileId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: "Unique identifier of the file to delete"
        - in: query
          name: errorLanguage
          schema:
            type: string
            enum: [en, sv, es]
          description: "Language for the error message"
      responses:
        '204':
          description: "File successfully deleted"
        '400':
          description: "Invalid request parameters"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: "Unauthorized - Invalid API keys or basic authentication"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '500':
          description: "Internal server error"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'
        '404':
          description: "Not Found - File not found or already deleted"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'
  
  /files/{fileId}/fields:
    get:
      tags:
        - files
      summary: "Retrieve all form fields for an uploaded file"
      description: |-
        Retrieves all the form fields defined in a specific file before it is used to create a document. This is useful for pre-filling values or understanding the structure of the form.

        ### Notes:
        - Fields retrieved here may include metadata such as `readOnly` (not possible to modify by the signee) and `editable` to indicate whether they can be modified or pre-filled.
        - For getting actual values in a signed document, use the `/docs/{documentId}/field-values` endpoint.

      operationId: getFileFields
      parameters:
        - name: fileId
          in: path
          required: true
          description: "The unique identifier of the uploaded file."
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: "Successfully retrieved form fields."
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FormField'
        '404':
          description: "File not found."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'
        '500':
          description: "Internal server error."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'

  /docs:
    post:
      tags:
        - docs
      summary: "Create a document and request signatures"
      description: |-
        This endpoint allows you to create a document and request signatures. You can:
        - Provide a `fileId` to create a document from an uploaded file.
        - Provide a `templateId` to create a document using a pre-configured template.

        ### Requirements
        - **fileId**: Use this to create a document from an uploaded file. Use `POST /files` to upload a file beforehand.
        - **templateId**: Use this to create a document from an existing template. Use `POST /templates` to create a template, and `GET /templates` to list templates.
        - **Either `fileId` or `templateId` must be provided**, but not both. If both are included, the request will fail with a `400 Bad Request` error.
        - **Template Feature Activation**:  
          The ability to use templates is only available for accounts with an active price plan. Contact Formify to activate this feature for your account if it is not already enabled.

        ### Ownership
        The **User ID** (`userId`) parameter can be optionally specified to assign the document ownership to a different user within the same account. If omitted, the document will be owned by the authenticated user. This user will receive document status updates by email.

        ### Signee Signature Type
        The **Signature Type** defines the method by which the signee will provide their signature. Specify the desired method using the `signatureType` parameter:
        1. **Digital Ink** (`digital_ink`):  
          The signee can draw their signature using a mouse, touchpad, or finger. This signature type complies with **Simple Electronic Signature (SES)** standards.
        2. **BankID Identification** (`bankid_identification`):
          The signee will securely sign using Swedish **BankID** for identification. This signature type complies with **Advanced Electronic Signature (AES)** standards.
          > **Note:** To achieve AES compliance for a document, all signatures within the document must be of the AES level.
          > **Availability:** BankID is only available for Swedish accounts or accounts that have this signature type enabled. Please contact Formify to request access.

        3. **ID Scan + Ink Signature** (`digital_ink_id_scan`):
          The signee verifies identity by scanning an ID document and then signs using digital ink.
          > **Placement requirement:** This signature type only supports `existing` placement and requires both `signatureBox` and `idScanBox`.
          > **Availability:** This signature type may require account enablement. Please contact Formify to request access.

        4. **Face Liveness** (`face_liveness`):
          The signee verifies identity using an ID scan together with a video selfie biometric liveness check.
          > **Availability:** This signature type may require account enablement. Please contact Formify to request access.

        ### Signature Field Placement
        You can choose one of two methods for placing signature fields on the document:

        1. **Automatic Placement** (`new_page`): The signature field will appear on a new page at the document's end (in A4 portrait format).
           - Not supported for `digital_ink_id_scan`.
        2. **Custom Placement with Coordinates (`existing`)**:
           - Use the `signatureBox` property to specify precise `x` and `y` coordinates, the `page` number, and an optional `scale` value.
           - Measurements are in points, and tools like Adobe Illustrator can help determine the exact position on the document.
           - Validation Rules:
             - `page` must be >= 0 and within the bounds of the document's page count.
             - `x` and `y` must be >= 0.
             - `scale` defaults to `1.0`, with a minimum of `0.25` and a maximum of `1.5`.
           - If `signatureBox` is not provided in the request:
             - Values will fall back to the template's `x`, `y`, `page`, `scale`, and `formFieldId`, if available.

        Each signature field is standardized in size before scaling:
        - **Width:** 219 points
        - **Height:** 58 points

        ### Additional Placement for ID Scan + Ink Signature
        When using `digital_ink_id_scan`, you must also provide an `idScanBox` inside the document.

        - `idScanBox` defines the position of the scanned ID placeholder.
        - `signaturePlacement` must be `existing` for this signature type.
        - Both `signatureBox` and `idScanBox` are required.
        - `idScanBox.scale` defaults to `1.0`, with a minimum of `0.25` and a maximum of `1.5`.

        The ID scan placeholder has the following base size before scaling:
        - **Width:** 218 points
        - **Height:** 138 points

        ### Signee Contact Methods
        Each signee must have at least one contact method specified to receive the document:
        - **Email Address** (`emailAddress`) or **Phone Number** (`phoneNumber`): Required for distributing the document. If using SMS notifications, additional transaction fees apply, and the SMS Invitation add-on must be active.
        - **Phone Numbers** must include the country code.
        - **Phone Text Message Delivery Method** (`phoneNumberDeliveryMethod`) is possible to set to either:  
          - **sms**: Sends a regular text message to the provided phone number (default - requires SMS Invitation add-on)
          - **WhatsApp**: Sends a WhatsApp text message to the provided phone number (requires WhatsApp Invitation add-on)

        By specifying the appropriate placement method and contact information, you ensure seamless delivery and positioning of signature fields in the document.

        ### Signing Order
        The **Signing Order** feature allows you to control the sequence in which signees receive invitations and can sign the document. This is useful when signatures must be collected in a specific order.
        - **Enabling Signing Order**: Set `enableSigningOrder` to `true` in the request to activate this feature.
        - **Setting Order Values**: Use the `signingOrder` property in each signee's details within the `signeeDetails` array to specify their position in the sequence.
          - **Sequential Signing**: Assign different order values (1, 2, 3, etc.) to enforce a strict signing sequence.
          - **Simultaneous Signing**: Assign the same order value to multiple signees to allow them to sign at the same time.
          - **Default Behavior**: If `enableSigningOrder` is `false` (default), all signees are treated as having order 1 and can sign simultaneously.
        - **Invitation Delivery**: Signees will only receive their invitation (email/SMS/WhatsApp) when it is their turn to sign based on their `signingOrder` value.
        - **Signing Order Feature Activation**:  
          The ability to use signing order is only available for accounts with an active price plan. Contact Formify to activate this feature for your account if it is not already enabled.

        ### Pre-filling Form Fields
        Use the optional `fields` property to pre-fill values in form fields:
        - **For `fileId`**: Use `GET /files/{fileId}/fields` to retrieve the list of fields in the uploaded file.
        - **For `templateId`**: Use `GET /templates/{templateId}/fields` to retrieve template-specific fields.
        
        Only editable fields (`editable` from `GET /files/{fileId}/fields` or `GET /templates/{templateId}/fields`) can be pre-filled through the API.

        ### When Using a Template
        - If you provide a `templateId`, the document will be created based on the specified template. This way, the template can place signature fields (and define the document's other fields/settings) in advance.
        - For each signee in the `signeeDetails` array:
          - **Full Name (`fullName`), Email Address (`emailAddress`), and Phone Number (`phoneNumber`)**:
            These should be provided in the request and will not fall back to the template. You must specify at least one contact method (email or phone) for each signee.
          - **Signature Type**:  
            Defaults to the template's value if not specified in the request.
          - **Signature Placement (`signaturePlacement`)**:  
            Defaults to the template's value if not specified in the request.
          - **Signature Box (`signatureBox`)**:
            The following properties will use the template defaults if not provided in the request:
            - `x`, `y`, `page`, `scale`
            - These are required for `existing` signature placement. If missing in both the request and template, the request will fail.
          - **ID Scan Box (`idScanBox`)**:
            Only applicable when `signatureType` is `digital_ink_id_scan`.
            - `x`, `y`, `page`, `scale`
            - These are required for `digital_ink_id_scan`. If missing in both the request and template, the request will fail.
        
        ### Form Fields Read-Only Mode (`fieldsReadonlyMode`)
          Determines how form fields should be handled in terms of read-only restrictions:

          1. **`keepOriginal`** (Default) – Retains the original field settings without modifying readonly state.
          2. **`filled`** – Set **pre-filled** fields readonly, leaving empty fields editable.
          3. **`all`** – Locks **all** fields, setting them readonly to prevent modifications.

        ### AI Assistant (`aiAssistant`)
          Configure an optional AI Assistant that helps signees while reviewing and signing the document.
          
          - **`enabled`**: Set to `true` to activate the assistant for this document (default: `false`)
          - **`textToSpeech`**: Enable text-to-speech so the assistant can read responses aloud (default: `false`)
          - **`language`**: Set the assistant's language (default: `en`)
          
          The AI Assistant can answer questions about the document content and guide signees through the signing process. It supports 28 languages including English, Swedish, Spanish, German, French, and more.

          **Important:** When the AI Assistant is enabled, the document creation is asynchronous:
          - The response will have `status: "processing"` instead of `status: "created"`
          - The `documentUrl` field will be `null` until processing is complete
          - Signee invitations (email/SMS/WhatsApp) will be sent automatically once processing is complete
          - Use `GET /docs/{documentId}` to check the document status
          
          > **Availability:** The AI Assistant feature may require account enablement. Please contact Formify to request access.          
          
      operationId: createDocument
      requestBody:
        content:
          application/json:
            example:
              fileId: "00000000-0000-0000-0000-000000000000"
              name: "Contract"
              signeeDetails:
                - fullName: "Jane Doe"
                  emailAddress: "jane.doe@example.com"
                  signaturePlacement: new_page
            schema:
              type: object
              oneOf:
                - required: ["fileId"]
                  properties:
                    fileId:
                      type: string
                      format: uuid
                      description: "The ID of the uploaded file. Mutually exclusive with `templateId`."
                - required: ["templateId"]
                  properties:
                    templateId:
                      type: string
                      format: uuid
                      description: |-
                        The ID of the template to use for creating the document. 
                        Mutually exclusive with `fileId`. 
                        Requires the templates feature to be enabled for your account."
              properties:
                name:
                  type: string
                  description: "Name of the document to be signed"
                userId:
                  type: string
                  format: uuid
                  description: "Optional User ID to assign as the document owner. If omitted, the authenticated user will be the owner."
                signeeDetails:
                  type: array
                  items:
                    $ref: '#/components/schemas/SignatureField'
                  minItems: 1
                  maxItems: 18
                enableSigningOrder:
                  type: boolean
                  default: false
                  description: |-
                    Enables sequential signing order for the document. When set to `true`, signees will be invited to sign based on their `signingOrder` value in the `signeeDetails` array.
                    
                    - **`false`** (default): All signees can sign simultaneously and receive invitations immediately
                    - **`true`**: Signees are invited based on their `signingOrder` value. Multiple signees with the same order value can sign simultaneously
                    
                    When enabled, signees will only receive their invitation (email/SMS/WhatsApp) when it's their turn to sign.
                fields:
                  type: array
                  items:
                    $ref: '#/components/schemas/FormFieldValue'
                fieldsReadonlyMode:
                  type: string
                  enum:
                    - keepOriginal
                    - filled
                    - all                    
                  default: keepOriginal
                  description: "Determines how form fields are handled in terms of read-only settings."
                language:
                  type: string
                  enum:
                    - en
                    - sv
                    - es
                  default: en
                  description: "Language of the signature request"
                sharingSetting:
                  type: string
                  enum:
                    - private
                    - shared
                  description: "Sharing setting for sharing the document between users within the company account. The default value depends on the account setting."
                personalMessage:
                  type: string
                  maxLength: 500
                  description: |-
                    Optional message to be included in the invitation sent to signees. This message can provide additional context or instructions.  
                    The message is plain text and will be displayed in the email or SMS/WhatsApp invitation (where supported).
                    The same message will be used for all signees, if more than one.
                aiAssistant:
                  $ref: '#/components/schemas/AiAssistant'
                errorLanguage:
                  type: string
                  enum: [en, sv, es]
                  description: "Language for the error message"
        required: true
      responses:
        '201':
          description: "Document created successfully"
          content:
            application/json:
              schema:
                type: object
                properties:
                  documentId:
                    type: string
                    format: uuid
                    description: "Unique identifier of the created document"
                  name:
                    type: string
                    description: "Name of the document"
                  createdAt:
                    type: string
                    format: date-time
                    description: "Document creation timestamp"
                  userId:
                    type: string
                    format: uuid
                    description: "User ID of the document owner"
                  status:
                    type: string
                    enum:
                      - created
                      - processing
                    description: |-
                      Status of the document creation:
                      - `created`: Document is ready and invitations have been sent to signees
                      - `processing`: Document is being processed (e.g., when AI Assistant is enabled). The document will be finalized asynchronously and invitations will be sent once processing is complete.
                  documentUrl:
                    type: string
                    format: uri
                    nullable: true
                    description: |-
                      Link to the sender's/owner's document overview page in Formify.
                      This field will be `null` when `status` is `processing` (e.g., when AI Assistant is enabled), as the document URL is not available until processing is complete.
        '400':
          description: "Invalid request parameters"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: "Unauthorized - Invalid API keys or basic authentication"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '500':
          description: "Internal server error"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'
        '404':
          description: "Not Found - File or template not found or already deleted"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'

    get:
      tags:
        - docs
      summary: "Get documents"
      description: "Get documents created by the user or shared within the account."
      operationId: getDocuments
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            default: 10
            minimum: 1
            maximum: 50
          description: "Number of documents to retrieve per request (max 50)"
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
            minimum: 0
          description: "Number of items to skip before starting to collect the result set"
        - in: query
          name: errorLanguage
          schema:
            type: string
            enum: [en, sv, es]
          description: "Language for the error message"
      responses:
        '200':
          description: "List of documents fetched successfully"
          content:
            application/json:
              schema:
                type: object
                properties:
                  limit:
                    type: integer
                    description: "The maximum number of documents requested per page"
                    example: 20                    
                  offset:
                    type: integer
                    description: "The offset used in the current request"
                    example: 0
                  nextOffset:
                    type: integer
                    nullable: true
                    description: "The next offset for pagination, or null if there are no more results"
                    example: null
                  documents:
                    type: array
                    items:
                      type: object
                      properties:
                        documentId:
                          type: string
                          format: uuid
                          description: "Document ID"
                        name:
                          type: string
                          description: "Name of the document"
                        createdAt:
                          type: string
                          format: date-time
                          description: "Document creation timestamp"
                        status:
                          type: string
                          enum:
                            - created
                            - processing
                            - awaiting_signatures
                            - completed
                            - revoked
                            - deleted
                          description: |-
                            Current status of the document:
                            - `created`: Document has been created and is ready
                            - `processing`: Document is being processed asynchronously
                            - `awaiting_signatures`: Document is awaiting signatures
                            - `completed`: All signatures collected
                            - `revoked`: Document revoked
                            - `deleted`: Document deleted
                        userId:
                          type: string
                          format: uuid
                          description: "User ID of the document owner"
                  errorLanguage:
                    type: string
                    enum: [en, sv, es]
                    description: "Language for the error message"
        '401':
          description: "Unauthorized - Invalid API keys or basic authentication"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '500':
          description: "Internal server error"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'

  /docs/{documentId}/signature-field:
    patch:
      tags:
        - docs
      summary: "Update signee details for a document"
      description: |-
        Updates signee details for an existing document.  

        ### Use Case:
        Updating the contact information for a signee (without updating the document content) is typically done when the sender has entered the wrong email address or phone number, preventing the signing party from accessing the document. It could also be done when the signing party has to be delegated to/replaced by another party.
        When there are updates to the content required, you must instead revoke the document and send a new one.

        **Notes:**
        - It's only possible to edit contact details, not to add new contact methods.
        - If **emailAddress** or **phoneNumber** is updated, a new document invitation will be sent to the signee with the updated contact details.
        - A **new `signeeId`** will be generated for the updated signee within this document.
        - Any previously sent document links to the old contact information will be **automatically revoked** to ensure security.
        - The document must not be completed or signed by the signee; otherwise, the request will fail with a `403 Forbidden` error.

        ### Cooldown Rules
        - **Update Limit**: A signee's contact details can only be updated a maximum of two times, before a **60-minute cooldown period** applies.
        - **Cooldown**: After updating a signature field twice, a **60-minute cooldown period** applies before another update can be made for the same signature field.
        - **Checking Remaining Cooldown**: The remaining cooldown time before another update is allowed is included in the response as `updates.updateCooldown` property in the signee details when retrieving document information via `GET /docs/{documentId}`.

      operationId: updateSignatureFields
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: "The unique identifier of the document."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                type: object
                properties:
                  signeeId:
                    type: string
                    format: uuid
                    description: "Unique identifier of the signee to update."
                  fullName:
                    type: string
                    nullable: true
                    description: "Updated full name of the signee. If null, the name remains unchanged."
                  emailAddress:
                    type: string
                    format: email
                    nullable: true
                    description: "Updated email address of the signee. If null, the email remains unchanged."
                  phoneNumber:
                    type: string
                    nullable: true
                    description: "Updated phone number of the signee. If null, the phone number remains unchanged."
            example:
              - signeeId: "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                fullName: "John Doe"
                emailAddress: "john.doe@example.com"
                phoneNumber: "+46701234567"
      responses:
        '200':
          description: "Signee details updated successfully."
          content:
            application/json:
              schema:
                type: object
                properties:
                  signeeId:
                    type: string
                    format: uuid
                    description: "Unique identifier of the updated signee."
                  updated:
                    type: object
                    properties:
                      fullName:
                        type: string
                        description: "Updated full name."
                        nullable: true
                      emailAddress:
                        type: string
                        format: email
                        description: "Updated email address."
                        nullable: true
                      phoneNumber:
                        type: string
                        description: "Updated phone number."
                        nullable: true
                  updateCooldown:
                    type: integer
                    description: "Remaining cooldown time in seconds before another signee update can be made."
              example:
                signeeId: "9c8704a4-2c62-4837-9b98-b27c00cd920f"
                updated:
                  emailAddress: "john.doe@example.com"
                updateCooldown: 3600
        '400':
          description: "Invalid request parameters."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: "Forbidden - The document is completed or the signee has already signed."
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  message:
                    type: string
                    example: "Cannot update signees for a completed document."
                  code:
                    type: integer
                    example: 403
        '404':
          description: "Document or signee not found."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'
        '429':
          description: "Too many updates - Cooldown period applies."
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  message:
                    type: string
                    example: "Update limit reached, try again later. Remaining cooldown: 3600 seconds"
                  code:
                    type: integer
                    example: 429
        '500':
          description: "Internal server error."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'



  /docs/{documentId}/reminder:
    post:
      tags:
        - docs
      summary: "Send reminders to specific signees who have not signed the document"
      description: |-
        Sends reminders to the specified signees for a document that is awaiting signatures. 

        ### Notes:
        - The document must be awaiting signatures (`awaiting_signatures`) to send reminders. Otherwise, the request will fail with `403 Forbidden`.
        - Reminders will be sent using all configured contact methods (e.g., email, SMS, WhatsApp) for each signee provided.
        - Reminders may incur SMS/WhatsApp fees, depending on the contact method. Ensure the account has sufficient balance.
        - The response includes a breakdown of which reminders were successfully sent and which were not, along with reasons for failures.

         ### Reminder Rules
        - **Reminder Limit**: A maximum of two reminders can be sent to a signee within a rolling 60-minute period.
        - **Cooldown**: After sending two reminders, a **60-minute cooldown period** applies before another reminder can be sent to the same signee.
        - **Checking Remaining Cooldown**: You can determine the remaining cooldown time for each signee by referencing the **`reminders.reminderCooldown`** property in the signee details when retrieving document information via `GET /docs/{documentId}`.

      operationId: sendReminder
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: "Unique identifier of the document."
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                errorLanguage:
                  type: string
                  enum: [en, sv, es]
                  description: "Language for the error message."
                signeeIds:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: "List of signee IDs to whom reminders will be sent."
              required:
                - signeeIds
        required: true
      responses:
        '200':
          description: |-
            Reminders processed successfully. Contains lists of signee IDs for whom reminders were successfully sent (`remindersSentTo`) 
            and those who could not be reminded (`remindersNotSentTo`) with reasons for failure.
          content:
            application/json:
              schema:
                type: object
                properties:
                  remindersSentTo:
                    type: array
                    description: "List of signee IDs who were successfully reminded."
                    items:
                      type: object
                      properties:
                        signeeId:
                          type: string
                          format: uuid
                          description: "The ID of the signee who was reminded."
                        reminderCooldown:
                          type: integer
                          description: "Remaining cooldown time in seconds before the next reminder can be sent."
                  remindersNotSentTo:
                    type: array
                    description: "List of signee IDs who could not be reminded, with reasons."
                    items:
                      type: object
                      properties:
                        signeeId:
                          type: string
                          format: uuid
                          description: "The ID of the signee who could not be reminded."
                        message:
                          type: string
                          description: "Reason why the signee could not be reminded."
                        reminderCooldown:
                          type: integer
                          description: "Remaining cooldown time in seconds before the next reminder can be sent."
        '400':
          description: |-
            Invalid request parameters, such as missing or invalid `signeeIds`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '402':
          description: |-
            Payment required. The account does not have enough balance to cover SMS/WhatsApp fees for sending reminders.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: |-
            Forbidden. Possible reasons:
            - The document is revoked or completed.
            - The user does not have permission to access the document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse403'
        '404':
          description: |-
            Document not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'
        '429':
          description: "Too many reminders - Cooldown period applies."
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  message:
                    type: string
                    example: "Reminder limit reached, try again later. Remaining cooldown: 3600 seconds"
                  code:
                    type: integer
                    example: 429
        '500':
          description: "Internal server error while processing the reminder request."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'


  /docs/{documentId}/revoke:
    put:
      tags:
        - docs
      summary: "Revoke a document"
      description: "Revoke a document that has been sent for signing. This operation is supported only if the document has not yet been fully signed. A revoked document can no longer be opened or signed, but it's not immediately deleted from the system."
      operationId: revokeDocumentById
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: "Unique identifier of the document to revoke "
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                errorLanguage:
                  type: string
                  enum: [en, sv, es]
                  description: "Language for the error message"
        required: true
      responses:
        '200':
          description: "Document revoked successfully"
          content:
            application/json:
              schema:
                type: object
                properties:
                  documentId:
                    type: string
                    format: uuid
                    description: "The unique identifier of the revoked document."
        '400':
          description: "Invalid request parameters"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: "Document not found"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'

  /docs/{documentId}/history:
    get:
      tags:
        - docs
      summary: "⚠️ Get document history"
      operationId: getDocumentHistory
      description: |-
        ⚠️ This endpoint is currently not available in production and is under development.

        Retrieves the event history for a specific document, including actions such as creation, 
        updates, signature requests, and completions. This is useful for auditing and tracking 
        document progress over time.
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: "The unique identifier of the document."
        - in: query
          name: errorLanguage
          schema:
            type: string
            enum: [en, sv, es]
          description: "Language for the error message"
      responses:
        '200':
          description: "Document history retrieved successfully"
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/DocumentHistoryEvent'
        '404':
          description: "Document not found"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'
        '500':
          description: "Internal server error"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'

  /docs/{documentId}/signed-file:
    get:
      tags:
        - docs
      summary: "Redirect to a URL for the signed PDF file download"
      description: "This endpoint generates a pre-signed URL for securely downloading a signed PDF file, valid for 10 minutes. It responds with a 302 status and a 'Location' header containing the URL for the client to follow."
      operationId: downloadSignedFile
      parameters:
        - name: documentId
          in: path
          required: true
          description: "ID of the document to download the signed file for"
          schema:
            type: string
            format: uuid
        - in: query
          name: errorLanguage
          schema:
            type: string
            enum: [en, sv, es]
          description: "Language for the error message"
      responses:
        '302':
          description: "Redirect to the pre-signed URL for the signed file"
          headers:
            Location:
              description: "URL to the signed file on Amazon S3"
              schema:
                type: string
        '400':
          description: "Bad Request - Invalid documentId or document is not signed"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: "Document not found"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'

  /docs/{documentId}:
    get:
      tags:
        - docs
      summary: "Get document details/status"
      description: |-
        Retrieves detailed information about a specific document, including its status, associated signatures, 
        and metadata. 
        
        This endpoint is useful for tracking the progress and current state of a document, as well as for 
        accessing specific metadata required for other operations.        
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: "The unique identifier of the document."
        - in: query
          name: errorLanguage
          schema:
            type: string
            enum: [en, sv, es]
          description: "Language for the error message"
      responses:
        '200':
          description: "Document details retrieved successfully"
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                    description: "Name associated with the document link"
                  createdAt:
                    type: string
                    format: date-time
                    description: "Timestamp when the document was created"
                  updatedAt:
                    type: string
                    format: date-time
                    description: "Timestamp when the document was last updated"
                  completedAt:
                    type: string
                    format: date-time
                    nullable: true
                    description: "Timestamp when the document was completed"
                  revokedAt:
                    type: string
                    format: date-time
                    nullable: true
                    description: "Timestamp when the document was revoked"
                  expiryDate:
                    type: string
                    format: date-time
                    nullable: true
                    description: "Timestamp when the document will expire, if applicable"
                  status:
                    type: string
                    enum: 
                      - created
                      - processing
                      - error
                      - awaiting_signatures
                      - completed
                      - revoked
                      - deleted                      
                    description: |-
                      Status identifier for the document:
                      - `created`: Document has been created and is ready
                      - `processing`: The document is being processed by the AI Assistant and is not yet ready.
                      - `error`: An error occurred during processing (e.g., AI Assistant failed).
                      - `awaiting_signatures`: Document is awaiting signatures from one or more signees
                      - `completed`: All signatures have been collected
                      - `revoked`: Document has been revoked
                      - `deleted`: Document has been deleted
                  aiAssistant:
                    $ref: '#/components/schemas/AiAssistantResponse'
                    description: "AI Assistant settings for this document. Only present if AI Assistant was enabled when the document was created. Returns `null` if AI Assistant was not used."
                  formifyTransactionId:
                    type: string
                    description: "Transaction ID associated with Formify"
                  language:
                    type: string
                    enum: [en, sv, es]
                    description: "Language code of the document"
                  enableSigningOrder:
                    type: boolean
                    description: "Indicates whether sequential signing order is enabled for this document. When true, signees are invited based on their signingOrder value."
                  signeeDetails:
                    type: array
                    description: "List of signees and their statuses"
                    items:
                      type: object
                      properties:
                        signeeId:
                          type: string
                          format: uuid
                          description: "Unique identifier of the signee"
                        fullName:
                          type: string
                          description: "Full name of the signee"
                        emailAddress:
                          type: string
                          format: email
                          description: "Email address of the signee"
                        phoneNumber:
                          type: string
                          description: "Phone number of the signee used for SMS"
                        signatureType:
                          type: string
                          enum: 
                            - not_set
                            - bankid_identification
                            - digital_ink
                            - digital_ink_id_scan
                            - face_liveness
                          description: "Type of signature"
                        signaturePlacement:
                          type: string
                          enum:
                            - existing
                            - new_page
                          default: existing
                          description: "Option used to place the signature box on an existing page ('existing', default) or adding a new signature page (A4 portrait format) at the end of the document ('new_page')."
                        signingOrder:
                          type: integer
                          minimum: 1
                          description: "The signing order assigned to this signee. Only relevant when signing order is enabled for the document. Signees with the same order value can sign simultaneously."
                        signatureStatus:
                          type: string
                          enum: 
                            - signed
                            - awaiting_signature
                          description: "Status of the signee's signature"
                        signedAt:
                          type: string
                          format: date-time
                          nullable: true
                          description: "Timestamp when the signee signed the document, if applicable"
                        reminders:
                          type: object
                          description: "Information about reminders sent to the signee"
                          properties:
                            reminderSentCount:
                              type: integer
                              description: "Number of reminders sent to this signee"
                            lastReminderSentAt:
                              type: string
                              format: date-time
                              nullable: true
                              description: "Timestamp of the most recent reminder sent to the signee, if applicable"
                            reminderCooldown:
                              type: integer
                              description: "Number of seconds remaining until a new reminder can be sent to the signee, or 0 if reminders can be sent immediately"
                        updates:
                          type: object
                          description: "Information about updates of the signee"
                          properties:
                            lastUpdateAt:
                              type: string
                              format: date-time
                              nullable: true
                              description: "Timestamp of the most recent update of the signee, if applicable"
                            updateCooldown:
                              type: integer
                              description: "Number of seconds remaining until a new update of the signee can be made, or 0 if updates can be done immediately"
                  userId:
                    type: string
                    format: uuid
                    description: "User ID of the document owner"
                  fileId:
                    type: string
                    format: uuid
                    description: "File ID used to create the document"
                  documentUrl:
                    type: string
                    format: uri
                    description: "Link to the document overview page in Formify"
                  templateId:
                    type: string
                    format: uuid
                    description: "Template ID used, if the document was created based on a template"
        '401':
          description: "Unauthorized - Invalid API keys or basic authentication"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '500':
          description: "Internal server error"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'
    delete:
      tags:
        - docs
      summary: "Delete a document"
      description: "Delete a document and its related file. This operation is only allowed if the document is completed or revoked. The file used to create the document signature request will be deleted only if it is not used in other documents."
      operationId: deleteDocumentById
      parameters:
        - name: documentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: "Unique identifier of the document to delete"
        - in: query
          name: errorLanguage
          schema:
            type: string
            enum: [en, sv, es]
          description: "Language for the error message"
      responses:
        '204':
          description: "Document successfully deleted"
        '400':
          description: "Invalid request parameters"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: "Unauthorized - Invalid API keys or basic authentication"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '500':
          description: "Internal server error"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'
        '404':
          description: "Not Found - Document not found or already deleted"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'

  /docs/{documentId}/fields:
    get:
      tags:
        - docs
      summary: "Retrieve all form fields for a document"
      description: |-
        Retrieves all the form fields defined for a specific document. This endpoint is particularly useful for extracting form field metadata before processing or integration.

        This endpoint is not for getting form field values. For that, use `/docs/{documentId}/field-values`.

        ### Supported Form Field Types
        The following form field types are possible:
        - **checkbox**: A field that allows a binary selection (checked or unchecked).
        - **combobox**: A dropdown field allowing selection of one option.
        - **listbox**: A list field allowing selection of one or multiple options.
        - **radioButton**: A field that allows a single selection from a group of options.
        - **textField**: A single-line input field for text.

        ### Notes:
        - **Required Fields**: A field is marked as required if the `required` property is `true`. This property is determined by the field's metadata.
        - **Read-Only Fields**: A field is marked as read-only if the `readOnly` property is `true`. Such fields cannot be modified by the end user, but can be modified through the API before a document has been sent for signing.
        - **Editable Fields**: A field can be entered/edited through the API if the `editable` property is `true`. For that, use `POST /docs/{documentId}/field-values`        
        - **Bounding Box**: The `boundingBox` property provides the position and dimensions of the form field on the document. The coordinates are measured in points.
        - **Default Values**: For applicable fields, `defaultValues` contains the default options or input values defined in the document.
        - **Options**: Dropdowns, radio buttons, and similar fields include a list of options with `label` and `value` pairs.
        
        ### Form Field Name as Key
        - **Key Usage**: The `name` property is used as the key for interacting with form field values instead of `fieldId`.
        - **Reason**: Using the `name` aligns with PDF standards and ensures compatibility with existing PDF workflows and tools.
        - **Advantages**:
          - **Consistency**: `name` is the standard identifier for form fields in PDFs.
          - **Human-Readable**: Names are easier to interpret compared to arbitrary IDs.
          - **Integration-Friendly**: Enables seamless integration with PDF tools that use names as primary identifiers.
        - **`fieldId` Purpose**: While the `fieldId` is included in the metadata for internal purposes, it is not used as the primary identifier in API interactions.
      operationId: getDocumentFields
      parameters:
        - name: documentId
          in: path
          required: true
          description: "The unique identifier of the document."
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: "Successfully retrieved form fields."
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FormField'
        '404':
          description: "Document not found."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'
        '500':
          description: "Internal server error."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'

  /docs/{documentId}/field-values:
    get:
      tags:
        - docs
      summary: "Retrieve form field values for a completed document"
      description: |-
        Retrieves all form field values for a specific completed document. 
        
        This endpoint is only supported for **completed documents**. If the document is not completed, the request will fail with a `403 Forbidden` error. 

        ### Notes:
        - **Non-Empty Fields**: Only fields with non-empty values will be included in the response.
        - **Value Format**: The `value` property is always an array of strings. For fields with a single value, the array will contain one element. For fields like checkboxes or multi-select dropdowns, the array may contain multiple elements.

        ### Example Response:
        ```json
        [
          {
            "name": "email",
            "value": ["test@example.com"]
          },
          {
            "name": "interests",
            "value": ["Photography", "Travel"]
          }
        ]
        ```

        The response does not include metadata or structural details about the form fields. For that, use `/docs/{documentId}/fields`.
      operationId: getFieldValues
      parameters:
        - name: documentId
          in: path
          required: true
          description: "The unique identifier of the document."
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: "Successfully retrieved field values for the document."
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    name:
                      type: string
                      description: "The name of the form field."
                    value:
                      type: array
                      items:
                        type: string
                      description: |-
                        An array of strings representing the field values. 
                        - For single-value fields (e.g., text fields), the array will have one element.
                        - For multi-select fields (e.g., checkboxes), the array can have multiple elements.
                      example:
                        - "test@example.com"
        '403':
          description: "Forbidden - Access to the document is denied or the document is not completed."
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  message:
                    type: string
                    example: "Unauthorized access to the file."
                  code:
                    type: integer
                    example: 403
        '404':
          description: "The document file was not found."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'
        '500':
          description: "Internal server error."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'
    post:
      tags:
        - docs
      summary: "Set form field values for a document"
      description: |-
        Updates the values of one or more form fields for a specific document.
        
        ### Usage:
        - This endpoint **only allows updating form fields**.
        - The document must **not be completed or signed by anyone**; otherwise, the request will fail with a `403 Forbidden` error.
        - Only fields where `editable = true` (from `GET /docs/{documentId}/fields`) can be updated.

        ### Dynamic Value Format:
        - The `value` property must match the expected format of the field.
        - **Single-value fields (e.g., text fields, radio buttons):** Provide a **single string**.
        - **Multi-value fields (e.g., checkboxes, listboxes):** Provide an **array of strings**, even if it only contains one string value.
        - To clear the values in a listbox or combobox, provide an array including a single empty string.
        - For radio buttons, an empty string clears the selection.
        - Get the expected format (and options, when available) for each field type using `GET /docs/{documentId}/fields`.

        ### Supported Field Types and Expected Formats:

        | Field Type          | Description                     | Example Format |
        |---------------------|---------------------------------|----------------|
        | `textField`         | A standard text input field.   | `"value": "John Doe"` |
        | `checkbox`          | A field supporting multiple selections (on/off). | `"value": ["Yes"]` |
        | `radioButton`       | A field where only one option can be selected. | `"value": "Red"` or `"value": ""` to clear selection |
        | `listbox`           | A list where users can select one or multiple options. | `"value": ["Option1", "Option2"]` or `"value": [""]` to clear values |
        | `combobox`          | A dropdown field allowing selection of one option. | `"value": ["SelectedOption"]` or `"value": [""]` to clear values  |
        
      operationId: setFieldValues
      parameters:
        - name: documentId
          in: path
          required: true
          description: "The unique identifier of the document."
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                fields:
                  type: array
                  items:
                    $ref: '#/components/schemas/FormFieldValue'         
      responses:
        '200':
          description: "Successfully updated form field values."
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
              example:
                success: true
        '400':
          description: "Invalid request parameters or field constraints violated."
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  message:
                    type: string
                    example: "Invalid field value provided."
                  code:
                    type: integer
                    example: 400
        '403':
          description: "Forbidden - The document is completed and cannot be modified."
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  message:
                    type: string
                    example: "Cannot update field values for a completed document."
                  code:
                    type: integer
                    example: 403
        '404':
          description: "The document or field was not found."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'
        '500':
          description: "Internal server error."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'

  /templates:
    get:
      tags:
        - templates
      summary: "List all templates"
      description: |-
        Retrieves a paginated list of all templates available to the user. 
        
        ### Notes:
        - Templates are reusable documents pre-configured for signature requests.
        - **Template Feature Activation**:  
          The ability to use templates is only available for accounts with an active price plan. Contact Formify to activate this feature for your account if it is not already enabled.

      operationId: getTemplates
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            default: 10
            minimum: 1
            maximum: 50
          description: "Number of documents to retrieve per request (max 50)"
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
            minimum: 0
          description: "Number of items to skip before starting to collect the result set"
        - in: query
          name: errorLanguage
          schema:
            type: string
            enum: [en, sv, es]
          description: "Language for the error message"
      responses:
        '200':
          description: "List of templates fetched successfully"
          content:
            application/json:
              schema:
                type: object
                properties:
                  limit:
                    type: integer
                    description: "The maximum number of templates requested per page"
                  offset:
                    type: integer
                    description: "The offset used in the current request"
                  nextOffset:
                    type: integer
                    nullable: true
                    description: "The next offset for pagination, or null if there are no more results"
                  templates:
                    type: array
                    items:
                      type: object
                      properties:
                        templateId:
                          type: string
                          format: uuid
                          description: "Template ID"
                        name:
                          type: string
                          description: "Name of the template"
                        createdAt:
                          type: string
                          format: date-time
                          description: "Template creation timestamp"
                        userId:
                          type: string
                          format: uuid
                          description: "User ID of the document owner"
        '401':
          description: "Unauthorized - Invalid API keys or basic authentication"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '500':
          description: "Internal server error"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'

  /templates/{templateId}:
    get:
      tags:
        - templates
      summary: "Retrieve template details"
      description: |-
        Retrieves detailed information about a specific template, including its fields, signees, and metadata.

        ### Notes:
        - Templates contain pre-configured signature fields and signee details.
        - You can use this endpoint to review the template's details before creating a document using the template.
      operationId: getTemplateDetails
      parameters:
        - name: templateId
          in: path
          required: true
          description: "The unique identifier of the template."
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: "Template details retrieved successfully"
          content:
            application/json:
              schema:
                type: object
                properties:
                  templateId:
                    type: string
                    format: uuid
                    description: "Unique identifier of the template"
                  name:
                    type: string
                    description: "Name of the template"
                  createdAt:
                    type: string
                    format: date-time
                    description: "Timestamp when the template was created"
                  userId:
                    type: string
                    format: uuid
                    description: "User ID of the document owner"
                  sharingSetting:
                    type: string
                    enum: [shared, private]
                    description: "Sharing setting for the template"
                  signeeDetails:
                    type: array
                    description: "List of signees and their default signature details"
                    items:
                      $ref: '#/components/schemas/SignatureField'
        '404':
          description: "Template not found"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'
        '401':
          description: "Unauthorized - Invalid API keys or basic authentication"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '500':
          description: "Internal server error"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'
  
  /templates/{templateId}/fields:
    get:
      tags:
        - templates
      summary: "Retrieve all form fields for a template"
      description: |-
        Retrieves all the form fields defined in a specific template. This endpoint is useful for extracting form field metadata before creating a document from the template.

        ### Supported Form Field Types
        The following form field types are possible:
        - **checkbox**: A field that allows a binary selection (checked or unchecked).
        - **combobox**: A dropdown field allowing selection of one option.
        - **listbox**: A list field allowing selection of one or multiple options.
        - **radioButton**: A field that allows a single selection from a group of options.
        - **textField**: A single-line input field for text.

        ### Notes:
        - **Required Fields**: A field is marked as required if the `required` property is `true`.
        - **Read-Only Fields**: A field is marked as read-only if the `readOnly` property is `true`.
        - **Editable Fields**: Fields with `editable = true` can be pre-filled when creating a document.
      operationId: getTemplateFields
      parameters:
        - name: templateId
          in: path
          required: true
          description: "The unique identifier of the template."
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: "Successfully retrieved form fields for the template."
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FormField'
        '404':
          description: "Template not found"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'
        '401':
          description: "Unauthorized - Invalid API keys or basic authentication"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '500':
          description: "Internal server error"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'



  /account/users:
    get:
      tags:
        - account
      summary: "Retrieve list of users on the account"
      description: "Retrieve a list of users on the account. Invited users with pending invitations are also included in the list (`invited`)."
      operationId: getAccountUsers
      parameters:
        - in: query
          name: errorLanguage
          schema:
            type: string
            enum: [en, sv, es]
          description: "Language for the error message"
      responses:
        '200':
          description: "List of account users retrieved successfully."
          content:
            application/json:
              schema:
                type: object
                properties:
                  users:
                    type: array
                    items:
                      type: object
                      properties:
                        userId:
                          type: string
                          format: uuid
                          description: "Unique identifier for the user."
                        email:
                          type: string
                          format: email
                          description: "Email address of the user."
                        firstName:
                          type: string
                          description: "User's first name."
                        lastName:
                          type: string
                          description: "User's last name."
                        fullName:
                          type: string
                          description: "User's full name, if available."
                        roles:
                          type: array
                          items:
                            type: string
                            enum: [admin, user]
                          description: "Roles assigned to the user within the account."
                        status:
                          type: string
                          enum: [active, invited]
                          description: "Status of the user on the account."
        '401':
          description: "Unauthorized - Invalid API keys or basic authentication"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '500':
          description: "Internal server error"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'
        '404':
          description: "Not Found - Account or users resource not found"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'

  /user:
      get:
        tags:
          - user
        summary: "Get user info settings"
        description: "Get info about the authenticated user."
        operationId: getUserInfo
        parameters:
          - in: query
            name: errorLanguage
            schema:
              type: string
              enum: [en, sv, es]
            description: "Language for the error message"
        responses:
          '200':
            description: "User info retrieved successfully"
            content:
              application/json:
                schema:
                  type: object
                  properties:
                    userId:
                      type: string
                      description: "Unique identifier of the user"
                    emailAddress:
                      type: string
                      description: "Email address of the user on this account"
                    fullName:
                      type: string
                      description: "Full name of the user"
                    firstName:
                      type: string
                      description: "First name of the user"
                    lastName:
                      type: string
                      description: "Last name of the user"
                    accountId:
                      type: string
                      description: "Unique identifier of the user's account"
                    accountName:
                      type: string
                      description: "Name of the user's account"
          '401':
            description: "Unauthorized - Invalid API keys or basic authentication"
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse401'
          '500':
            description: "Internal server error"
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse500'

  /webhooks:
      post:
        tags:
          - webhooks
        summary: "Register a new webhook"
        description: |
          Register a new webhook to receive event notifications.

          ### About Webhooks
          Webhooks allow Formify to communicate with your system using callbacks triggered by specified Formify events. Webhooks provide real-time data updates, enabling you to integrate Formify events directly into your workflows.

          #### What’s the use for webhooks?
          With webhooks, you can be notified of specific events to automate workflows such as sending documents for signature or tracking their status in real-time.

          #### How do I set up webhooks?
          When you create a webhook, specify the events you'd like to subscribe to. To reduce unnecessary requests to your server, only subscribe to the events you plan to handle.

          ### Entity Types
          Formify uses entity types to filter webhook events based on the scope of data you want to receive. The entity type defines which object changes in Formify trigger your webhook.

          | Entity Type       | Description                                                                                                 | Example                                                               |
          |-------------------|-------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------|
          | **Document**      | Subscribe to events involving a specific document.                                                          | A document is sent for signature, signed, or completed (sealed). Each change triggers your webhook. |

          ### Supported Events
          - `document.created`: A document has been created/sent for signature.
          - `document.opened`: A document has been opened.
          - `document.signed`: A document has been signed by a signee.
          - `document.completed`: A document process has been signed by all parties and is sealed.
          - `document.revoked`: A document has been revoked.
          - `document.deleted`: A document has been deleted.

          ### Notes
          - Other entity types (e.g., `settings.x`) are not yet supported.
          - Do not assume ordering: Events may arrive late, more than once, or out of order

          ### Webhook Request Payload          
          - See the `DocumentWebhookEvent` schema for the structure of the Document entity type payload.

          #### Example for General Events (`document.created`, `document.opened`, etc.)
          ```json
          {
            "event": {
              "eventId": "123e4567-e89b-12d3-a456-426614174000",
              "type": "document.created",
              "date": "2024-10-25T14:23:55Z",
              "data": {
                "documentId": "789e4567-e89b-12d3-a456-426614174111",
                "accountId": "456e4567-e89b-12d3-a456-426614174222",
                "userId": "123e4567-e89b-12d3-a456-426614174333"
              }
            }
          }
          ```

          - **eventId**: The unique identifier for the event instance.
          - **type**: The type of the event that triggered the webhook.
          - **date**: The date and time the event was triggered in ISO 8601 format.
          - **data**: Contains the `documentId`, `userId` and `accountId` relevant to the event.

          #### Example for `document.signed` Event
          ```json
          {
            "event": {
              "eventId": "123e4567-e89b-12d3-a456-426614174000",
              "type": "document.signed",
              "date": "2024-10-25T14:23:55Z",
              "data": {
                "documentId": "789e4567-e89b-12d3-a456-426614174111",
                "accountId": "456e4567-e89b-12d3-a456-426614174222",
                "userId": "123e4567-e89b-12d3-a456-426614174333",
                "signeeId": "789e4567-e89b-12d3-a456-426614174555"
              }
            }
          }
          ```

          ### Notes
          - All event payloads include `eventId`, `type`, `date`, and a `data` object with event-specific details.
          - The data object may differ between events and the `signeeId` field is only included for the `document.signed` event.

          ### Expected Response for Webhooks
          To acknowledge receipt of the webhook, your server should respond with an HTTP status code in the 2xx range. No specific content is required in the response body; a `200 OK` status alone will indicate successful processing.

          #### Example Response
          ```http
          HTTP/1.1 200 OK
          Content-Type: application/json
          ```

          ### Retry Attempts
          Formify will attempt to deliver webhook events up to 5 times if the initial attempt fails. The retry intervals* are as follows:
          - First retry: "immediately"
          - Second retry: after ~5 minutes
          - Third retry: after ~30 minutes 
          - Fourth retry: after ~2 hours
          - Fifth retry: after ~24 hours

          *The intervals are not exact and may vary slightly. After 5 retries, the event will not be sent again.

          Formify must receive a success status code (2xx) from your server to acknowledge the event. If a success status code is not received, Formify will retry sending the event according to the intervals specified above.

          Each event sent by Formify includes a unique `eventId`. You can use this `eventId` to ensure that your system processes each event only once and to avoid handling duplicate events.

          ### Security and Reliability

          To secure your webhook endpoint:

          - **Use HTTPS**: Always use HTTPS URLs for your webhook endpoint to encrypt data in transit
          - **Verify webhook signatures**: Use HMAC-SHA256 to verify that requests are authentic (see Webhook Signature Verification below)
          - **Use eventId for idempotency**: Store processed `eventId` values to safely ignore duplicate deliveries

          ### Webhook Delivery Headers

          Every webhook delivery includes the following headers:

          | Header | Type | Description |
          |---|---|---|
          | `Content-Type` | string | Always `application/json` |
          | `X-Formify-Timestamp` | string | Unix timestamp (seconds) of when the event was sent |
          | `X-Formify-Signature` | string | Lowercase hexadecimal HMAC-SHA256 digest of `{timestamp}.{raw_body}` |

          ### Webhook Signature Verification

          Formify signs all webhook payloads using HMAC-SHA256 so you can verify that requests are authentic. Each webhook endpoint is assigned a unique signing secret (prefixed with `whsec_`) upon creation.

          **How to verify:**

          The signature is computed over the string `{timestamp}.{raw_body}` using your webhook's signing secret as the HMAC key.

          **Example (Node.js with Express):**
          ```javascript
          const crypto = require('crypto');
          const express = require('express');

          const app = express();

          app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
            const timestamp = req.headers['x-formify-timestamp'];
            const signature = req.headers['x-formify-signature'];
            const secret = 'whsec_...'; // your signing secret

            const signedPayload = `${timestamp}.${req.body}`;
            const expectedSignature = crypto
              .createHmac('sha256', secret)
              .update(signedPayload)
              .digest('hex');

            const isValid = crypto.timingSafeEqual(
              Buffer.from(expectedSignature),
              Buffer.from(signature)
            );

            if (!isValid) {
              return res.status(401).send('Invalid signature');
            }

            const event = JSON.parse(req.body);

            // Process the event...
            res.status(200).send('OK');
          });
          ```

          **Important:** Use the raw request body exactly as received when verifying the signature. Do not verify against a re-serialized JSON payload.

          **Signature format:** The `X-Formify-Signature` header contains the lowercase hexadecimal HMAC-SHA256 digest of:

          ```
          {timestamp}.{raw_body}
          ```

          **Timestamp verification:** You should also verify that the `X-Formify-Timestamp` value is recent to reduce replay attacks.

          ### Testing Webhooks

          For local development, you can use tools such as [ngrok](https://ngrok.com) to expose your local server to the internet and receive webhook calls from Formify.

          Example:

              ngrok http 3000

          This gives you a public HTTPS URL that you can register as your webhook endpoint for testing.

          You can also use request inspection tools such as:

          - [webhook.site](https://webhook.site) — Inspect webhook payloads without writing code
          - [requestbin.com](https://requestbin.com) — Capture and inspect HTTP requests

          These tools are useful for development and debugging before you build your own webhook handler.

          **Typical test flow**
          1. Register a webhook endpoint
          2. Trigger an event, for example by creating or signing a document
          3. Verify that the webhook payload is received
          4. Return a `2xx` response from your server

          > **Note:** These tools are useful for development and debugging. Production webhook endpoints should use your own secure, publicly accessible HTTPS endpoint.

          ### Idempotency

          Webhooks may be delivered more than once due to network issues or retries. To handle this:

          ```javascript
          // Example: Node.js/Express webhook handler
          app.post('/webhook', async (req, res) => {
            const { event } = req.body;
            const eventId = event.eventId;

            // Check if we've already processed this event
            const alreadyProcessed = await db.checkEventId(eventId);
            if (alreadyProcessed) {
              console.log(`Event ${eventId} already processed, skipping`);
              return res.status(200).send('OK');
            }

            // Store eventId to prevent duplicate processing
            await db.storeEventId(eventId);

            // Process the event
            await handleEvent(event);

            res.status(200).send('OK');
          });
          ```

          ### Debugging

          **Common Issues:**

          | Issue | Cause | Solution |
          |-------|-------|----------|
          | Webhook not received | URL incorrect or server down | Verify URL is accessible from the internet |
          | Webhook retrying repeatedly | Server not returning 2xx status | Check server logs, ensure 200/201/204 response |
          | Duplicate events | Not tracking eventId | Implement idempotency using eventId |
          | Events out of order | Network delays | Don't rely on event order; use timestamps |

          **Logging:**
          - Log all incoming webhook requests with timestamp, eventId, and event type
          - Log your server's response status code

          **Monitoring:**
          - Track webhook delivery success rate in Formify dashboard (if available)
          - Set up alerts if webhook endpoint returns errors consistently
          - Monitor processing time to ensure webhooks are handled quickly
        operationId: registerWebhook
        requestBody:
          content:
            application/json:
              schema:
                type: object
                properties:
                  webhookUrl:
                    type: string
                    format: uri
                    description: "The URL where webhook events will be sent."
                  name:
                    type: string
                    description: "The name/alias of the webhook for you to identify it."
                  eventTypes:
                    type: array
                    items:
                      type: string
                      enum:
                        - document.created
                        - document.opened
                        - document.signed
                        - document.completed
                        - document.revoked
                        - document.deleted                        
                    description: "A list of event types that the webhook will be subscribed to."
          required: true
        responses:
          '201':
            description: "Webhook registered successfully."
            content:
              application/json:
                schema:
                  type: object
                  properties:
                    webhookId:
                      type: string
                      format: uuid
                      description: "The unique identifier of the registered webhook."
                    eventTypes:
                      type: array
                      items:
                        type: string
                        enum:
                          - document.created
                          - document.opened
                          - document.signed
                          - document.completed
                          - document.revoked
                          - document.deleted
                      description: "A list of event types the webhook is subscribed to."
                    signingSecret:
                      type: string
                      description: |-
                        HMAC signing secret for this webhook. Store this securely — it is used to verify webhook payloads.
                        Prefixed with `whsec_`.
                      example: "whsec_dGhpcyBpcyBhIHNhbXBsZSBzZWNyZXQ="
                example:
                  webhookId: "123e4567-e89b-12d3-a456-426614174000"
                  eventTypes: ["document.opened", "document.signed"]
                  signingSecret: "whsec_dGhpcyBpcyBhIHNhbXBsZSBzZWNyZXQ="
          '401':
            description: "Unauthorized - Invalid API keys or basic authentication."
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse401'
          '500':
            description: "Internal server error."
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse500'
      get:
        tags:
          - webhooks
        summary: "Get all registered webhooks"
        description: "This endpoint retrieves all registered webhooks."
        operationId: getAllWebhooks
        parameters:
          - in: query
            name: errorLanguage
            schema:
              type: string
              enum: [en, sv, es]
            description: "The language for error messages."
        responses:
          '200':
            description: "List of registered webhooks fetched successfully."
            content:
              application/json:
                schema:
                  type: array
                  items:
                    type: object
                    properties:
                      webhookId:
                        type: string
                        format: uuid
                        description: "The unique identifier of the webhook."
                      webhookUrl:
                        type: string
                        format: uri
                        description: "The URL where webhook events are sent."
                      name:
                        type: string
                        description: "The name of the webhook."
                      eventTypes:
                        type: array
                        items:
                          type: string
                          description: "The event types that the webhook is subscribed to."
                      signingSecret:
                        type: string
                        description: "HMAC signing secret for this webhook."
                        example: "whsec_dGhpcyBpcyBhIHNhbXBsZSBzZWNyZXQ="
          '401':
            description: "Unauthorized - Invalid API keys or basic authentication."
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse401'
          '500':
            description: "Internal server error."
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse500'
  /webhooks/{webhookId}:
      get:
        tags:
          - webhooks
        summary: "Get a specific webhook"
        description: "This endpoint retrieves a specific webhook by its unique identifier."
        operationId: getWebhookById
        parameters:
          - in: path
            name: webhookId
            required: true
            schema:
              type: string
              format: uuid
            description: "The unique identifier of the webhook to retrieve."
        responses:
          '200':
            description: "Webhook retrieved successfully."
            content:
              application/json:
                schema:
                  type: object
                  properties:
                    webhookId:
                      type: string
                      format: uuid
                      description: "The unique identifier of the webhook."
                    webhookUrl:
                      type: string
                      format: uri
                      description: "The URL where webhook events are sent."
                    name:
                      type: string
                      description: "The name of the webhook."
                    eventTypes:
                      type: array
                      items:
                        type: string
                        description: "The event types that the webhook is subscribed to."
                    hasSigningSecret:
                      type: boolean
                      description: "Whether HMAC signing secret is used for this webhook."
                      example: true
          '404':
            description: "Not Found - Webhook not found."
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse404'
          '401':
            description: "Unauthorized - Invalid API keys or basic authentication."
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse401'
          '500':
            description: "Internal server error."
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse500'
      delete:
        tags:
          - webhooks
        summary: "Delete a webhook"
        description: "This endpoint deletes a registered webhook."
        operationId: deleteWebhook
        parameters:
          - in: path
            name: webhookId
            required: true
            schema:
              type: string
              format: uuid
            description: "The unique identifier of the webhook to delete."
        responses:
          '204':
            description: "Webhook deleted successfully. No content returned."
          '404':
            description: "Not Found - Webhook not found."
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse404'
          '401':
            description: "Unauthorized - Invalid API keys or basic authentication."
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse401'
          '500':
            description: "Internal server error."
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorResponse500'
  /webhooks/{webhookId}/rotate-secret:
    post:
      tags:
        - webhooks
      summary: "Rotate webhook signing secret"
      description: |-
        Generates a new signing secret for the specified webhook. The old secret is immediately invalidated. Make sure to update your verification logic with the new secret.
      operationId: rotateWebhookSecret
      parameters:
        - name: webhookId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: "The unique identifier of the webhook."
        - name: errorLanguage
          in: query
          schema:
            type: string
            enum: [en, sv, es]
            default: en
          description: "Language for the error message"
      responses:
        '200':
          description: "Secret rotated successfully."
          content:
            application/json:
              schema:
                type: object
                properties:
                  webhookId:
                    type: string
                    format: uuid
                    description: "The unique identifier of the webhook."
                  signingSecret:
                    type: string
                    description: "The new signing secret."
                    example: "whsec_bmV3IHNlY3JldCBnZW5lcmF0ZWQ="
        '404':
          description: "Webhook not found or disabled."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse404'
        '403':
          description: "Forbidden — webhook belongs to another account."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse403'
        '401':
          description: "Unauthorized - Invalid API keys or basic authentication."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse401'
        '500':
          description: "Internal server error."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse500'