openapi: 3.0.3
info:
  # Stoplight renders `info.title` as a sidebar brand header; VitePress already provides page branding.
  title: ""
  description: |
    # API reference

    **Upsign API v2** is documented here as an interactive reference: every operation, parameter, request body, and response schema in one place.

    Before you integrate in code, read **[Getting started](/guide/getting-started)** for base URLs and flow, **[Authentication](/guide/authentication)** for **`X-Api-Key`** and partner headers on mutating requests, and **[Errors](/guide/errors)** for HTTP status codes and JSON error shapes.

    ## Navigate this reference

    Use the **left sidebar** to move around:

    - **Overview** — This introduction (you are here).
    - **Endpoints** — **`Documents`** and **`Webhooks`** list every HTTP operation. Expand an operation to see parameters, bodies, and responses.
    - **Schemas** — Shared models (for example **`ErrorBody`**, **`CodedError`**, webhook payloads); operations link here when they reuse a model.

    The URL **hash** updates when you open an operation so you can bookmark or share a specific endpoint.

    ## Try requests with **Try it**

    Each operation includes **Try it** so you can send real requests from the browser when this origin is allowed by **CORS** on your API. Set your base URL (production or a local stack), send **`X-Api-Key`**, and include **`X-Partner-User-Id`** (plus optional partner headers) on **`POST`**, **`PUT`**, **`PATCH`**, and **`DELETE`** as described in **[Authentication](/guide/authentication)**.

    ## Errors and responses

    Failed requests use conventional HTTP status codes and JSON bodies. See **[Errors](/guide/errors)** for typical meanings; each operation documents its responses and points at shared schemas where they apply.

    ## OpenAPI specification

    For client generation, CI validation, or tooling outside this UI, use the **[OpenAPI](/guide/openapi)** guide and the **`openapi.yaml`** file served from this documentation site (and **`GET /v2/openapi.yaml`** from the API when your deployment exposes it).
  # Empty so Stoplight Overview does not render a version pill (still valid OpenAPI string).
  version: ""
  # Omit info.license so Stoplight Overview does not show an “Additional information” block.
  # Terms: https://www.upsign.com/terms

servers:
  - url: https://{host}/v2
    variables:
      host:
        default: api.upsign.com
        description: Production API host (replace with your deployment host).
  - url: http://localhost:8080/v2
    description: Local development (example).

tags:
  - name: Documents
    description: Create and manage documents (`doc_*` public IDs).
  - name: Webhooks
    description: Manage webhook subscriptions (`wh_*`). **Admin only.**

security:
  - ApiKeyAuth: []

paths:
  /documents:
    post:
      tags: [Documents]
      operationId: createDocument
      summary: Create document
      description: |
        Creates a draft document. Accepts **`multipart/form-data`** with **`title`** (required), optional **`file`**, optional **`recipients`** JSON string.

        **Mutating:** requires **`X-Partner-User-Id`** per middleware.
      parameters:
        - $ref: "#/components/parameters/XPartnerUserIdRequired"
        - $ref: "#/components/parameters/XPartnerUserEmail"
        - $ref: "#/components/parameters/XPartnerRequestId"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [title]
              properties:
                title:
                  type: string
                  example: NDA Agreement
                file:
                  type: string
                  format: binary
                  description: Optional upload (pdf, images, office types per server validation).
                recipients:
                  type: string
                  description: JSON array string, e.g. `[{"email":"a@b.com","name":"A","role":"SIGNER"}]`
                  example: '[{"email":"john@example.com","name":"John Doe","role":"signer"}]'
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    example: doc_abc123
                  status:
                    type: string
                    example: draft
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorBody"
              examples:
                missing_fields:
                  value: { code: missing_fields, message: "Title is required." }
                invalid_file_type:
                  value: { code: invalid_file_type, message: "Unsupported file type: xyz" }
                invalid_recipients_format:
                  value: { code: invalid_recipients_format, message: "Recipients must be a valid JSON array." }
        "500":
          description: Server error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorBody"

  /documents/{public_id}:
    get:
      tags: [Documents]
      operationId: getDocument
      summary: Get document
      description: Returns document details if `public_id` starts with **`doc_`** and is accessible to the token’s company.
      parameters:
        - name: public_id
          in: path
          required: true
          description: Document public ID (`doc_*`).
          schema:
            type: string
            example: doc_abc123
        - name: locale
          in: query
          required: false
          schema:
            type: string
          description: Locale for localized status label (defaults to acting user locale).
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentDetailResponse"
        "400":
          description: Invalid ID format
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodedError"
        "404":
          description: Not found or not accessible
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodedError"

    put:
      tags: [Documents]
      operationId: updateDocumentTitle
      summary: Update document title
      description: |
        **`application/json`** only (not multipart). Updates title and fires **`document.updated`** webhook when successful.

        **Mutating:** requires **`X-Partner-User-Id`**.
      parameters:
        - $ref: "#/components/parameters/XPartnerUserIdRequired"
        - $ref: "#/components/parameters/XPartnerUserEmail"
        - $ref: "#/components/parameters/XPartnerRequestId"
        - name: public_id
          in: path
          required: true
          description: Document public ID (`doc_*`).
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [title]
              properties:
                title:
                  type: string
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string }
                  title: { type: string }
                  updated_at: { type: string, format: date-time }
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodedError"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodedError"

  /documents/{public_id}/remind:
    post:
      tags: [Documents]
      operationId: remindDocument
      summary: Send reminder
      description: |
        Resends to incomplete recipients when status is **`OUT_FOR_SIGNATURE`**.

        **Mutating:** requires **`X-Partner-User-Id`**.
      parameters:
        - $ref: "#/components/parameters/XPartnerUserIdRequired"
        - $ref: "#/components/parameters/XPartnerUserEmail"
        - $ref: "#/components/parameters/XPartnerRequestId"
        - name: public_id
          in: path
          required: true
          description: Document public ID (`doc_*`).
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                locale:
                  type: string
                  description: Locale key for send (server default if omitted).
                ip_info: { type: object }
                device:
                  type: string
                  example: desktop
      responses:
        "200":
          description: Reminder sent
          content:
            application/json:
              schema:
                type: object
                properties:
                  document_id: { type: string }
                  sent_to:
                    type: array
                    items: { type: string }
                  timestamp: { type: string, format: date-time }
        "400":
          description: Invalid status or ID
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodedError"
        "401":
          description: Send failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodedError"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodedError"

  /webhooks:
    get:
      tags: [Webhooks]
      operationId: listWebhooks
      summary: List webhooks
      description: Lists company webhooks. **Admin access required.** Does not return `secret_key`.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/WebhookSummary"
        "401":
          $ref: "#/components/responses/UnauthorizedJson"
        "403":
          $ref: "#/components/responses/ForbiddenAdminJson"

    post:
      tags: [Webhooks]
      operationId: createWebhook
      summary: Create webhook
      description: |
        Registers a webhook. **`secret_key`** is returned **only in this response** if auto-generated or supplied.

        **Mutating:** requires **`X-Partner-User-Id`**.
      parameters:
        - $ref: "#/components/parameters/XPartnerUserIdRequired"
        - $ref: "#/components/parameters/XPartnerUserEmail"
        - $ref: "#/components/parameters/XPartnerRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url:
                  type: string
                  format: uri
                type:
                  type: string
                  default: document
                  description: Subscription type (e.g. document vs template channel).
                secret_key:
                  type: string
                  description: Optional; server generates if omitted.
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookCreated"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodedError"
        "401":
          $ref: "#/components/responses/UnauthorizedJson"
        "403":
          $ref: "#/components/responses/ForbiddenAdminJson"

  /webhooks/{public_id}:
    get:
      tags: [Webhooks]
      operationId: getWebhook
      summary: Get webhook
      description: Fetch one webhook by **`wh_*`** id. **Admin only.** Does not return `secret_key`.
      parameters:
        - name: public_id
          in: path
          required: true
          description: Webhook subscription public ID (`wh_*`).
          schema:
            type: string
            example: wh_abc123
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSummary"
        "400":
          description: Invalid ID format
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodedError"
        "401":
          $ref: "#/components/responses/UnauthorizedJson"
        "403":
          $ref: "#/components/responses/ForbiddenAdminJson"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodedError"

    delete:
      tags: [Webhooks]
      operationId: deleteWebhook
      summary: Delete webhook
      description: Deletes subscription. **Admin only.**

        **Mutating:** requires **`X-Partner-User-Id`**.
      parameters:
        - $ref: "#/components/parameters/XPartnerUserIdRequired"
        - $ref: "#/components/parameters/XPartnerUserEmail"
        - $ref: "#/components/parameters/XPartnerRequestId"
        - name: public_id
          in: path
          required: true
          description: Webhook subscription public ID (`wh_*`).
          schema:
            type: string
      responses:
        "204":
          description: Deleted
        "400":
          description: Invalid ID format
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodedError"
        "401":
          $ref: "#/components/responses/UnauthorizedJson"
        "403":
          $ref: "#/components/responses/ForbiddenAdminJson"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodedError"

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-Api-Key

  parameters:
    XPartnerUserIdRequired:
      name: X-Partner-User-Id
      in: header
      required: true
      schema:
        type: string
      description: Required for POST, PUT, PATCH, DELETE (partner end-user attribution).
    XPartnerUserEmail:
      name: X-Partner-User-Email
      in: header
      required: false
      description: Optional partner end-user email for logging/attribution.
      schema:
        type: string
    XPartnerRequestId:
      name: X-Partner-Request-Id
      in: header
      required: false
      schema:
        type: string
      description: Idempotency / tracing; server may generate if omitted.

  schemas:
    ErrorBody:
      type: object
      properties:
        error:
          type: string
    CodedError:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
    DocumentDetailResponse:
      type: object
      properties:
        id:
          type: string
        title:
          type: string
        status:
          description: Localized status (structure depends on server).
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        owner:
          type: object
          properties:
            name: { type: string }
            email: { type: string }
        recipients:
          type: array
          items:
            type: object

    WebhookSummary:
      type: object
      properties:
        id:
          type: string
          example: wh_abc123
        url:
          type: string
          format: uri
        type:
          type: string
        has_secret:
          type: boolean
        created_at:
          type: string
          format: date-time

    WebhookCreated:
      allOf:
        - $ref: "#/components/schemas/WebhookSummary"
        - type: object
          properties:
            secret_key:
              type: string
              description: Shown only once at creation.

  responses:
    UnauthorizedJson:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorBody"
          example:
            error: Unauthorized
    ForbiddenAdminJson:
      description: Forbidden — admin required
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorBody"
          example:
            error: "Forbidden. Admin access required."
