{
  "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"
                }
              }
            }
          }
        }
      }
    },
    "/imports": {
      "get": {
        "operationId": "listImports",
        "summary": "List imports",
        "description": "Your bulk imports, newest first, cursor-paginated. An account that has never imported returns an empty list, never a 404.",
        "tags": [
          "Imports"
        ],
        "security": [
          {
            "bearerAuth": [
              "imports:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many imports 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. Any unknown value rejects the whole request. One of: running, finished.",
            "schema": {
              "type": "string",
              "example": "running"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of imports.",
            "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/ImportList"
                }
              }
            }
          },
          "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": "startImport",
        "summary": "Start an import",
        "description": "Brings documents into XOsign from DocuSign, Google Drive or Dropbox.\n\n**DocuSign** (`source: docusign`) sweeps the connected account and files every completed envelope's documents, in the background. It returns immediately; poll the returned import to watch it. The account must already have connected DocuSign in Settings: that is an OAuth sign-in through a browser and cannot be done with a key.\n\n**Google Drive and Dropbox** (`source: google_drive` or `dropbox`) import up to 25 files you name and answer when they are in — usually within seconds. Give your client at least 90 seconds before it times out; if it does time out, retry with the same `Idempotency-Key` to receive the answer. The answer carries every file's outcome in `items`, in the order you sent them, and one file failing never stops the rest. A file you have imported before comes back as the same document rather than a copy, so after a partial failure you can send the whole list again — with a new `Idempotency-Key`, or none, since the same key replays the earlier answer. For Drive, send a Google access token your own app minted; it is used for this request and kept nowhere. For Dropbox, send file links you already hold; nothing on our side connects to your Dropbox. Needs the Pro plan or above. A request naming N files spends N of your rate limit, and every file counts against your plan's import limits: files per day and storage per month. A file that comes back as a document you imported before uses no storage again, and a retry that replays an earlier answer spends one unit of your rate limit, not N.\n\nEach file is also unlocked as an XOdoc, which is what the Unlock box (checked by default) does on an import in the app; that conversion is charged at 1 credit a page when it runs. Send `unlock_xodoc: false` to file them as they are, for no credits.",
        "tags": [
          "Imports"
        ],
        "security": [
          {
            "bearerAuth": [
              "imports: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/StartImportRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The import. A DocuSign migration has every count at zero and runs on; a Google Drive or Dropbox import is normally already finished and carries `items`.",
            "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/StartedImport"
                }
              }
            }
          },
          "400": {
            "description": "The body was rejected, and nothing was created. Codes: invalid_source, invalid_earliest, docusign_not_connected, invalid_files, invalid_file_url, invalid_file_id, invalid_access_token, invalid_unlock_xodoc, google_token_rejected.",
            "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, cloud_import_requires_pro, cloud_import_unavailable, cloud_import_test_key, import_quota_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A migration is already running for this connection, or an idempotency conflict. A retry with the same key while a Google Drive or Dropbox import is still running gets idempotency_in_progress; retry a few seconds later to receive its answer. Codes: import_already_running, 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"
                }
              }
            }
          },
          "502": {
            "description": "Nothing was started. Codes: import_not_started, google_unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/imports/{id}": {
      "get": {
        "operationId": "getImport",
        "summary": "Retrieve an import",
        "description": "Poll this while a migration runs. `remaining` reaching zero with `status: finished` is the end; `total_files` grows while a DocuSign sweep is still listing.",
        "tags": [
          "Imports"
        ],
        "security": [
          {
            "bearerAuth": [
              "imports:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The import id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The import.",
            "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/Import"
                }
              }
            }
          },
          "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 import with that id. Code: import_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"
                }
              }
            }
          }
        }
      }
    },
    "/imports/{id}/items": {
      "get": {
        "operationId": "listImportItems",
        "summary": "List an import's files",
        "description": "Every file inside one import, each with its own outcome, oldest first. For an import started from Google Drive or Dropbox that is the order you sent the files, and each carries its `index` in that request. A DocuSign migration or a browser drop lists its files here the same way, while it runs and after.",
        "tags": [
          "Imports"
        ],
        "security": [
          {
            "bearerAuth": [
              "imports:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The import id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many files 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"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of files.",
            "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/ImportItemList"
                }
              }
            }
          },
          "400": {
            "description": "Bad pagination. Codes: invalid_limit, invalid_cursor, conflicting_cursors.",
            "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 import with that id. Code: import_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",
                "imports:read",
                "imports:write"
              ]
            },
            "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",
              "comment_letter"
            ],
            "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"
            ]
          }
        }
      },
      "Import": {
        "type": "object",
        "description": "One bulk import: a DocuSign migration, or a batch of files dropped or picked from cloud storage. All four counts come from a SQL aggregate, so they are exact however large the import is.",
        "required": [
          "object",
          "id",
          "source",
          "status",
          "total_files",
          "filed",
          "failed",
          "remaining",
          "created_at",
          "finished_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "import"
            ]
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "source": {
            "type": "string",
            "enum": [
              "docusign",
              "google_drive",
              "dropbox",
              "upload"
            ],
            "description": "`docusign` is a swept migration. `google_drive` and `dropbox` are imports started through this API. `upload` is everything else feeding the same lane: a browser drop, or a Drive or Dropbox pick made in the app. New values may be added; treat one you do not recognise as `upload`."
          },
          "status": {
            "type": "string",
            "enum": [
              "running",
              "finished"
            ],
            "description": "`finished` means every file is filed or failed. An import that finished with failures is still `finished` — the counts say what happened."
          },
          "total_files": {
            "type": "integer",
            "description": "How many files the lane knows about. On a DocuSign migration this GROWS while the sweep is still listing envelopes, so do not treat it as a fixed denominator until `status` is `finished` — poll `remaining` instead."
          },
          "filed": {
            "type": "integer",
            "description": "Files that landed as documents."
          },
          "failed": {
            "type": "integer",
            "description": "Files that did not land. Each carries its own reason on the import screen."
          },
          "remaining": {
            "type": "integer",
            "description": "Still queued or in flight. Zero with `status: finished` is the end."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ImportList": {
        "type": "object",
        "required": [
          "object",
          "data",
          "has_more",
          "next_cursor"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Import"
            }
          },
          "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."
          }
        }
      },
      "ImportItem": {
        "type": "object",
        "description": "One file inside an import, with its own outcome — one file's failure never hides another's.",
        "required": [
          "object",
          "id",
          "index",
          "file_name",
          "status",
          "document_id",
          "failure_code",
          "failure_reason",
          "unlock"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "import_item"
            ]
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "index": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The file's position (from 0) in the `files` list of the request that started the import, so you can match each result to what you sent. Null for files that arrived any other way."
          },
          "file_name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "working",
              "filed",
              "failed"
            ],
            "description": "A Drive or Dropbox import answers with every file `filed` or `failed`. `queued` and `working` appear while a DocuSign run or a browser drop is still going."
          },
          "document_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The document the file became. A file imported before, from the same source, answers with the document that import made instead of a copy."
          },
          "failure_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set only on a failed file. One of: not_found (a Dropbox link that does not exist, has expired or was turned off, or a Drive id the token cannot see), no_access, unsupported_type, too_large (over 150 MB, or a Google export over 10 MB), download_failed, provider_blocked (Google flagged the file as malware or spam), timeout (the request's time ran out before this file), interrupted (the request was cut off; send the file again), filing_failed. New codes may be added."
          },
          "failure_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "One plain sentence saying what happened and what to do. Failed files only."
          },
          "unlock": {
            "type": [
              "string",
              "null"
            ],
            "description": "What came of `unlock_xodoc` for this file: `queued` (its conversion to an XOdoc is on the way and is charged by pages as it runs), `declined` (this file cannot be unlocked, for example a document that already has a body of its own), or `failed` (nothing was queued; unlock it from the document). Null when `unlock_xodoc` was false."
          }
        }
      },
      "ImportItemList": {
        "type": "object",
        "required": [
          "object",
          "data",
          "has_more",
          "next_cursor"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ImportItem"
            }
          },
          "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."
          }
        }
      },
      "StartedImport": {
        "type": "object",
        "description": "What starting an import answers. A DocuSign migration answers with the Import, every count at zero, and runs in the background. A Google Drive or Dropbox import answers when its files are in: the Import, normally already `finished`, plus `items` — every file's outcome, in the order you sent them.",
        "required": [
          "object",
          "id",
          "source",
          "status",
          "total_files",
          "filed",
          "failed",
          "remaining",
          "created_at",
          "finished_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "import"
            ]
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "source": {
            "type": "string",
            "enum": [
              "docusign",
              "google_drive",
              "dropbox",
              "upload"
            ],
            "description": "`docusign` is a swept migration. `google_drive` and `dropbox` are imports started through this API. `upload` is everything else feeding the same lane: a browser drop, or a Drive or Dropbox pick made in the app. New values may be added; treat one you do not recognise as `upload`."
          },
          "status": {
            "type": "string",
            "enum": [
              "running",
              "finished"
            ],
            "description": "`finished` means every file is filed or failed. An import that finished with failures is still `finished` — the counts say what happened."
          },
          "total_files": {
            "type": "integer",
            "description": "How many files the lane knows about. On a DocuSign migration this GROWS while the sweep is still listing envelopes, so do not treat it as a fixed denominator until `status` is `finished` — poll `remaining` instead."
          },
          "filed": {
            "type": "integer",
            "description": "Files that landed as documents."
          },
          "failed": {
            "type": "integer",
            "description": "Files that did not land. Each carries its own reason on the import screen."
          },
          "remaining": {
            "type": "integer",
            "description": "Still queued or in flight. Zero with `status: finished` is the end."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ImportItem"
            },
            "description": "Present for a Google Drive or Dropbox import; absent for DocuSign."
          }
        }
      },
      "StartImportRequest": {
        "type": "object",
        "required": [
          "source"
        ],
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "docusign",
              "google_drive",
              "dropbox"
            ],
            "description": "`docusign` sweeps an account that is already connected in Settings (that sign-in is an OAuth redirect through a browser, so a key cannot make it) and runs in the background. `google_drive` and `dropbox` import the files you name, and answer when they are in."
          },
          "earliest": {
            "type": "string",
            "format": "date-time",
            "description": "DocuSign only. Bring envelopes completed on or after this instant. Omit to sweep everything."
          },
          "access_token": {
            "type": "string",
            "description": "Google Drive only, and required there. A Google OAuth access token minted by your own Google app with the `drive.file` or `drive.readonly` scope. It is used for this request only and never stored or logged. Google tokens last about an hour; a retry with a refreshed token counts as the same request for idempotency."
          },
          "files": {
            "type": "array",
            "minItems": 1,
            "maxItems": 25,
            "description": "Google Drive and Dropbox, and required there: the files to import, each up to 150 MB. PDFs are accepted; Google Docs, Sheets and Slides arrive as PDFs exported by Google, which caps an export at 10 MB. Other kinds of file, and folders, come back failed with `unsupported_type`. What a file is gets decided from its contents, never its name.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Google Drive: the file's id."
                },
                "url": {
                  "type": "string",
                  "format": "uri",
                  "description": "Dropbox: a link to one file that you already hold — a shared link (`https://www.dropbox.com/scl/fi/…`), the Chooser's direct link, or one from `files/get_temporary_link`. Chooser and temporary links expire after four hours. Folder, Transfer and tracking links are refused. No Dropbox account is connected on our side, so anyone holding a link can import the file it names — only send links to files your user chose."
                },
                "name": {
                  "type": "string",
                  "maxLength": 400,
                  "description": "Dropbox, optional: the name to file it under. Otherwise the name Dropbox gives the download, or the link's own, is used."
                }
              }
            }
          },
          "unlock_xodoc": {
            "type": "boolean",
            "default": true,
            "description": "Google Drive and Dropbox. Unlock each file as an XOdoc — what the Unlock box, checked by default, does on an import in the app. Each file reports what came of it in `unlock`, and the conversion is charged at 1 credit a page when it runs, as the app charges it. Send `false` to file them as they are: they consume no credits, and any of them can be unlocked from the document later."
          }
        }
      },
      "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",
              "comment_letter"
            ],
            "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."
              }
            }
          }
        }
      }
    }
  }
}