{
  "openapi": "3.1.0",
  "info": {
    "title": "XOsign API",
    "version": "1.0.0",
    "summary": "Create and send documents for signature.",
    "description": "The XOsign public API. Server-to-server only — there is no CORS allowance, so these endpoints cannot be called from browser JavaScript.\n\nAuthenticate with a bearer API key from Settings → API keys. All times are RFC 3339 UTC.",
    "contact": {
      "name": "XOsign support",
      "url": "https://xosign.ai/help"
    },
    "license": {
      "name": "Proprietary",
      "identifier": "LicenseRef-Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://xosign.ai/api/v1",
      "description": "Production. This is the only supported host — calling a deployment URL directly makes clients drop the Authorization header across the redirect."
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "Who the calling key belongs to."
    },
    {
      "name": "Documents",
      "description": "Create, list, retrieve, and send documents."
    },
    {
      "name": "Recipients",
      "description": "The parties on a document."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/account": {
      "get": {
        "operationId": "getAccount",
        "summary": "Retrieve the current account",
        "description": "Returns the account behind the API key, plus the key's mode and scopes. The cheapest way to verify a key works and to see what it is allowed to do. Requires no scope.",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The account.",
            "headers": {
              "X-Request-Id": {
                "description": "Unique id for this request. Quote it in any support conversation — it is how we find your exact request.",
                "schema": {
                  "type": "string",
                  "example": "req_b07b069f0a83492877e3fa31"
                }
              },
              "X-RateLimit-Limit-Burst": {
                "description": "Your plan's ceiling for the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Burst": {
                "description": "Requests left in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit-Daily": {
                "description": "Your plan's ceiling for the current UTC day.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Daily": {
                "description": "Requests left today. Resets at 00:00 UTC.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the burst window rolls over.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "description": "The request carried no usable API key, so we could not tell who you are. Codes: missing_authorization, invalid_api_key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "We know who you are, but this key or this plan is not allowed to do that. Codes: insufficient_scope, api_access_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "You sent more requests than your plan allows. Carries Retry-After. Codes: rate_limit_exceeded, ip_throttled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something failed on our side. Nothing is wrong with your request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/documents": {
      "get": {
        "operationId": "listDocuments",
        "summary": "List documents",
        "description": "Your documents, newest first, cursor-paginated. An account with no documents returns an empty list, never a 404. Templates are not included.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "bearerAuth": [
              "documents:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many documents to return, 1–100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "A cursor from a previous response's `next_cursor`. Returns the page after it. Cannot be combined with `ending_before`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ending_before",
            "in": "query",
            "required": false,
            "description": "A cursor from a previous response. Returns the page before it. Cannot be combined with `starting_after`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Comma-separated statuses to include, e.g. `sent,completed`. Any unknown value rejects the whole request. One of: draft, sent, partially_signed, completed, voided, declined, expired, in_review.",
            "schema": {
              "type": "string",
              "example": "sent,completed"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of documents.",
            "headers": {
              "X-Request-Id": {
                "description": "Unique id for this request. Quote it in any support conversation — it is how we find your exact request.",
                "schema": {
                  "type": "string",
                  "example": "req_b07b069f0a83492877e3fa31"
                }
              },
              "X-RateLimit-Limit-Burst": {
                "description": "Your plan's ceiling for the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Burst": {
                "description": "Requests left in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit-Daily": {
                "description": "Your plan's ceiling for the current UTC day.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Daily": {
                "description": "Requests left today. Resets at 00:00 UTC.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the burst window rolls over.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentList"
                }
              }
            }
          },
          "400": {
            "description": "Bad pagination or status filter. Codes: invalid_limit, invalid_cursor, conflicting_cursors, invalid_status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request carried no usable API key, so we could not tell who you are. Codes: missing_authorization, invalid_api_key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "We know who you are, but this key or this plan is not allowed to do that. Codes: insufficient_scope, api_access_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "You sent more requests than your plan allows. Carries Retry-After. Codes: rate_limit_exceeded, ip_throttled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something failed on our side. Nothing is wrong with your request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createDocument",
        "summary": "Create a draft document",
        "description": "Creates a draft and its recipients in one call. Nothing is delivered until you call send.\n\nField placement is not supported through the API — `content` accepts only the small metadata bag described below, and unknown properties are dropped silently.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "bearerAuth": [
              "documents:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Optional but strongly recommended on writes. A client-chosen string of at most 255 characters. Retrying with the same key and the same body replays the original response instead of performing the write twice.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDocumentRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created draft.",
            "headers": {
              "X-Request-Id": {
                "description": "Unique id for this request. Quote it in any support conversation — it is how we find your exact request.",
                "schema": {
                  "type": "string",
                  "example": "req_b07b069f0a83492877e3fa31"
                }
              },
              "X-RateLimit-Limit-Burst": {
                "description": "Your plan's ceiling for the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Burst": {
                "description": "Requests left in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit-Daily": {
                "description": "Your plan's ceiling for the current UTC day.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Daily": {
                "description": "Requests left today. Resets at 00:00 UTC.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the burst window rolls over.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "400": {
            "description": "The body was rejected. `param` names the exact field. See /docs/api/errors for every code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request carried no usable API key, so we could not tell who you are. Codes: missing_authorization, invalid_api_key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "We know who you are, but this key or this plan is not allowed to do that. Codes: insufficient_scope, api_access_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict. Codes: idempotency_key_reused, idempotency_in_progress.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "You sent more requests than your plan allows. Carries Retry-After. Codes: rate_limit_exceeded, ip_throttled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something failed on our side. Nothing is wrong with your request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/documents/{id}": {
      "get": {
        "operationId": "getDocument",
        "summary": "Retrieve a document",
        "description": "Poll this to watch a document progress from `sent` to `completed`.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "bearerAuth": [
              "documents:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The document id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The document.",
            "headers": {
              "X-Request-Id": {
                "description": "Unique id for this request. Quote it in any support conversation — it is how we find your exact request.",
                "schema": {
                  "type": "string",
                  "example": "req_b07b069f0a83492877e3fa31"
                }
              },
              "X-RateLimit-Limit-Burst": {
                "description": "Your plan's ceiling for the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Burst": {
                "description": "Requests left in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit-Daily": {
                "description": "Your plan's ceiling for the current UTC day.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Daily": {
                "description": "Requests left today. Resets at 00:00 UTC.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the burst window rolls over.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "401": {
            "description": "The request carried no usable API key, so we could not tell who you are. Codes: missing_authorization, invalid_api_key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "We know who you are, but this key or this plan is not allowed to do that. Codes: insufficient_scope, api_access_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such document is visible to your account. Also returned for a malformed id and for templates. Code: document_not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "You sent more requests than your plan allows. Carries Retry-After. Codes: rate_limit_exceeded, ip_throttled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something failed on our side. Nothing is wrong with your request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/documents/{id}/send": {
      "post": {
        "operationId": "sendDocument",
        "summary": "Send a document for signature",
        "description": "Moves a draft to `sent` and delivers a signature request to each recipient over its channel.\n\nTakes no body — send options come from the `content` you supplied at creation. Only a draft or in-review document can be sent; sending an already-sent document returns `document_not_sendable`.\n\nA test-mode key still transitions the document to `sent` but delivers no email or SMS.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "bearerAuth": [
              "documents:send"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The document id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Optional but strongly recommended on writes. A client-chosen string of at most 255 characters. Retrying with the same key and the same body replays the original response instead of performing the write twice.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Send an empty object. No properties are read.",
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The document, now `sent`.",
            "headers": {
              "X-Request-Id": {
                "description": "Unique id for this request. Quote it in any support conversation — it is how we find your exact request.",
                "schema": {
                  "type": "string",
                  "example": "req_b07b069f0a83492877e3fa31"
                }
              },
              "X-RateLimit-Limit-Burst": {
                "description": "Your plan's ceiling for the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Burst": {
                "description": "Requests left in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit-Daily": {
                "description": "Your plan's ceiling for the current UTC day.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Daily": {
                "description": "Requests left today. Resets at 00:00 UTC.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the burst window rolls over.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "400": {
            "description": "The document cannot be sent as it stands. Codes: document_not_sendable, no_recipients, no_signers, recipient_contact_required, approver_requires_sequential, pay_app_unsigned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request carried no usable API key, so we could not tell who you are. Codes: missing_authorization, invalid_api_key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "We know who you are, but this key or this plan is not allowed to do that. Codes: insufficient_scope, api_access_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such document. Code: document_not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict. Codes: idempotency_key_reused, idempotency_in_progress.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "You sent more requests than your plan allows. Carries Retry-After. Codes: rate_limit_exceeded, ip_throttled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something failed on our side. Nothing is wrong with your request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/documents/{id}/recipients": {
      "get": {
        "operationId": "listDocumentRecipients",
        "summary": "List a document's recipients",
        "description": "Per-recipient progress: who was sent to, who opened it, who signed, who declined.\n\nThis needs its own `recipients:read` scope, separate from `documents:read`, so a key can list documents without reading signer contact details.",
        "tags": [
          "Recipients"
        ],
        "security": [
          {
            "bearerAuth": [
              "recipients:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The document id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The recipients, ordered by `sign_order`. A document with none returns an empty list.",
            "headers": {
              "X-Request-Id": {
                "description": "Unique id for this request. Quote it in any support conversation — it is how we find your exact request.",
                "schema": {
                  "type": "string",
                  "example": "req_b07b069f0a83492877e3fa31"
                }
              },
              "X-RateLimit-Limit-Burst": {
                "description": "Your plan's ceiling for the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Burst": {
                "description": "Requests left in the current one-minute window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit-Daily": {
                "description": "Your plan's ceiling for the current UTC day.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Daily": {
                "description": "Requests left today. Resets at 00:00 UTC.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the burst window rolls over.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipientList"
                }
              }
            }
          },
          "401": {
            "description": "The request carried no usable API key, so we could not tell who you are. Codes: missing_authorization, invalid_api_key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "We know who you are, but this key or this plan is not allowed to do that. Codes: insufficient_scope, api_access_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such document. Code: document_not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "You sent more requests than your plan allows. Carries Retry-After. Codes: rate_limit_exceeded, ip_throttled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something failed on our side. Nothing is wrong with your request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your API key from Settings → API keys, sent as `Authorization: Bearer xo_live_…`. Live keys start `xo_live_`; test keys start `xo_test_`."
      }
    },
    "schemas": {
      "Account": {
        "type": "object",
        "description": "The account the calling API key belongs to.",
        "required": [
          "object",
          "id",
          "display_name",
          "tier",
          "created_at",
          "mode",
          "scopes"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "account"
            ]
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "display_name": {
            "type": "string"
          },
          "tier": {
            "type": "string",
            "description": "The account's current plan. Determines your rate limits."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "Echoes which kind of key made this request."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "documents:read",
                "recipients:read",
                "documents:write",
                "documents:send"
              ]
            },
            "description": "The scopes granted to the key that made this request."
          }
        }
      },
      "Document": {
        "type": "object",
        "description": "A document in your account.",
        "required": [
          "object",
          "id",
          "type",
          "status",
          "title",
          "description",
          "primary_language",
          "secondary_language",
          "recipient_count",
          "sent_at",
          "completed_at",
          "voided_at",
          "voided_reason",
          "expires_at",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "document"
            ]
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "enum": [
              "contract",
              "form",
              "letter",
              "estimate",
              "change_order",
              "invoice",
              "lien_waiver",
              "notice_to_owner",
              "schedule_of_values",
              "pay_app",
              "coi",
              "license",
              "other",
              "w9",
              "warranty",
              "completion_certificate",
              "safety_acknowledgment",
              "general",
              "receipt",
              "note",
              "check",
              "notice_of_commencement"
            ],
            "description": "The kind of document. Determines which workflows apply to it."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "sent",
              "partially_signed",
              "completed",
              "voided",
              "declined",
              "expired",
              "in_review"
            ]
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "primary_language": {
            "type": "string",
            "enum": [
              "en",
              "es"
            ]
          },
          "secondary_language": {
            "type": [
              "string",
              "null"
            ]
          },
          "recipient_count": {
            "type": "integer",
            "description": "How many recipients are on the document."
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Set when every signer has signed."
          },
          "voided_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "voided_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Recipient": {
        "type": "object",
        "description": "A party on a document. Signing links and any authentication material are never exposed.",
        "required": [
          "object",
          "id",
          "document_id",
          "display_name",
          "email",
          "phone",
          "party_role",
          "channel",
          "sign_order",
          "auth_level",
          "status",
          "sent_at",
          "viewed_at",
          "signed_at",
          "declined_at",
          "declined_reason"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "recipient"
            ]
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "document_id": {
            "type": "string",
            "format": "uuid"
          },
          "display_name": {
            "type": "string"
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "party_role": {
            "type": [
              "string",
              "null"
            ]
          },
          "channel": {
            "type": "string",
            "enum": [
              "email",
              "sms",
              "in_person"
            ]
          },
          "sign_order": {
            "type": "integer"
          },
          "auth_level": {
            "type": "string",
            "enum": [
              "l0_email_link",
              "l1_email_sms_otp",
              "l0_account_session"
            ]
          },
          "status": {
            "type": "string"
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "viewed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "signed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "declined_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "declined_reason": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "DocumentList": {
        "type": "object",
        "required": [
          "object",
          "data",
          "has_more",
          "next_cursor"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Document"
            }
          },
          "has_more": {
            "type": "boolean",
            "description": "Whether another page exists after this one."
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass as `starting_after` to fetch the next page. Null when `has_more` is false. Opaque — never construct or parse one."
          }
        }
      },
      "RecipientList": {
        "type": "object",
        "description": "Recipient sets are small and bounded, so this list is never paginated: `has_more` is always false and `next_cursor` always null.",
        "required": [
          "object",
          "data",
          "has_more",
          "next_cursor"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Recipient"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "CreateDocumentRequest": {
        "type": "object",
        "required": [
          "title",
          "recipients"
        ],
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "type": {
            "type": "string",
            "enum": [
              "contract",
              "form",
              "letter",
              "estimate",
              "change_order",
              "invoice",
              "lien_waiver",
              "notice_to_owner",
              "schedule_of_values",
              "pay_app",
              "coi",
              "license",
              "other",
              "w9",
              "warranty",
              "completion_certificate",
              "safety_acknowledgment",
              "general",
              "receipt",
              "note",
              "check",
              "notice_of_commencement"
            ],
            "default": "form",
            "description": "Defaults to `form`."
          },
          "primary_language": {
            "type": "string",
            "enum": [
              "en",
              "es"
            ],
            "default": "en"
          },
          "recipients": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/CreateRecipient"
            }
          },
          "content": {
            "$ref": "#/components/schemas/DocumentContent"
          }
        }
      },
      "CreateRecipient": {
        "type": "object",
        "required": [
          "name",
          "channel"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 120
          },
          "channel": {
            "type": "string",
            "enum": [
              "email",
              "sms",
              "in_person"
            ]
          },
          "email": {
            "type": "string",
            "maxLength": 254,
            "description": "Required when `channel` is `email`."
          },
          "phone": {
            "type": "string",
            "maxLength": 40,
            "description": "Required when `channel` is `sms`."
          },
          "role": {
            "type": "string",
            "maxLength": 60,
            "description": "Free-text party role, e.g. `signer` or `approver`."
          },
          "sign_order": {
            "type": "integer",
            "minimum": 1,
            "description": "Defaults to this recipient's 1-based position in the array. Only meaningful when `content.sign_order_mode` is `sequential`."
          },
          "auth_level": {
            "type": "string",
            "enum": [
              "l0_email_link",
              "l1_email_sms_otp",
              "l0_account_session"
            ],
            "default": "l0_email_link",
            "description": "How hard the signer must prove who they are. `l1_email_sms_otp` adds a one-time code."
          }
        }
      },
      "DocumentContent": {
        "type": "object",
        "description": "A small metadata bag. Any property not listed here is silently dropped rather than rejected — v1 does not accept field placement or annotations through the API.",
        "properties": {
          "message": {
            "type": "string",
            "maxLength": 2000,
            "description": "A note included in the signature request."
          },
          "sign_order_mode": {
            "type": "string",
            "enum": [
              "sequential",
              "parallel"
            ],
            "description": "`sequential` sends to one recipient at a time in `sign_order`; `parallel` sends to everyone at once."
          },
          "expires_in_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365
          }
        }
      },
      "Error": {
        "type": "object",
        "description": "Every failure uses this shape. Branch on `type` and `code`; treat `message` as human-facing text that may be reworded.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "type",
              "code",
              "message",
              "doc_url"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "authentication_error",
                  "permission_error",
                  "validation_error",
                  "not_found",
                  "rate_limit_error",
                  "idempotency_error",
                  "api_error"
                ],
                "description": "The error family. Maps 1:1 to the HTTP status."
              },
              "code": {
                "type": "string",
                "description": "The specific, stable reason. Safe to branch on. See /docs/api/errors for the full catalog."
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation."
              },
              "param": {
                "type": "string",
                "description": "The offending field, when one can be named — including the array index, e.g. `recipients[2].email`."
              },
              "doc_url": {
                "type": "string",
                "format": "uri",
                "description": "Link to the documentation for this error type."
              },
              "request_id": {
                "type": "string",
                "description": "Matches the `X-Request-Id` response header."
              }
            }
          }
        }
      }
    }
  }
}