{
  "openapi": "3.1.0",
  "info": {
    "title": "Signclad API",
    "version": "1.0.0",
    "summary": "eSignature API for documents, templates, bulk sends, and webhooks.",
    "description": "Signclad's `/api/v1` is the single source of truth for request validation, reference\ndocumentation, generated SDKs, and MCP tool schemas.\n\n## Compatibility\n\nEvery operation that is **not** marked `x-signclad-extension: true` is wire-compatible with\nthe third-party API described in `docs/compatibility-reference.md`: same paths, methods,\nrequest fields, response fields, status strings, error envelopes, and webhook event names.\nA client that works against that API works against Signclad after changing the base URL and\nthe API key.\n\nBehavior that OpenAPI cannot express, and that Signclad guarantees anyway:\n\n- **Trailing slashes.** Every path routes with and without a trailing slash\n  (`/documents/{id}` and `/documents/{id}/`).\n- **Unknown keys are ignored.** No request is ever rejected for carrying a property this\n  document does not define. Every request body schema is `additionalProperties: true`.\n- **`metadata` values are strings.** Up to 50 pairs, key under 40 characters, value under\n  500 characters.\n- **`completed_pdf` returns 404**, not 403, until every signer has finished.\n- **Status casing** is returned exactly as the enums in this document spell it.\n\n## Roles, capabilities, and plans\n\nEvery request resolves to an actor and a workspace. A **role** grants capabilities:\n\n| Capability | owner | admin | member | viewer |\n|---|---|---|---|---|\n| `view_documents` | ✓ | ✓ | ✓ | ✓ |\n| `send_documents` | ✓ | ✓ | ✓ | – |\n| `manage_templates` | ✓ | ✓ | ✓ | – |\n| `manage_members` | ✓ | ✓ | – | – |\n| `manage_settings` | ✓ | ✓ | – | – |\n| `manage_billing` | ✓ | – | – | – |\n\nAn **API key acts as an admin** — `GET /me` reports `role: admin` for one, and compatibility\nclients depend on that — but never gets `manage_billing`: a leaked integration key must not be\nable to cancel the subscription, transfer ownership, or delete a workspace. A missing\ncapability is `403 insufficient_permissions` with the capability named. A feature the plan does\nnot include is `422 not_available_on_plan`; an account behind on billing is\n`422 account_read_only`; templates over the plan's cap are `422 template_read_only` rather than\ndeleted.\n\n## Extensions\n\nOperations, parameters, and response fields marked `x-signclad-extension: true` are additive.\nThey never change the behavior of a compatibility request that does not use them. Extension\noperations are grouped under the `Extensions` tag.\n\n## Headers\n\n| Header | Direction | Meaning |\n|---|---|---|\n| `X-Api-Key` | request | API key. Sandbox keys are prefixed `sk_test_`, live keys `sk_live_`. |\n| `Api-Version` | request | Dated API version, e.g. `2026-09-09`. **Not yet implemented**: nothing reads it and no response returns one. |\n| `Idempotency-Key` | request | Safe retry for any `POST`/`PATCH`/`PUT`. Scoped to **credential** + method + path — an API key, an OAuth token or a session each get their own — and kept 24 hours. A replayed response carries `Idempotent-Replay: true`. |\n| `X-Signclad-Source` | request | Provenance — `mcp` or `zapier`, nothing else. Validated on **every** request, so an unrecognized value is `422 errors.source` whatever the route. It sets `documents.source` and affects nothing else. |\n| `X-Workspace-Id` | request | For a session caller in several workspaces, which one this request is for. A workspace the caller is not a live member of is ignored rather than refused, and their current workspace is used. Ignored for API keys, which belong to one workspace already. |\n| `x-ratelimit-limit`, `x-ratelimit-remaining`, `x-ratelimit-reset` | response | On every `/api/v1` response. The root-mounted routes — sign-in, OAuth, SSO, SCIM — use their own named limiters and carry none of these. `x-ratelimit-reset` is an **ISO 8601 timestamp** (`2026-09-09T20:56:00+00:00`), not a number of seconds. |\n| `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` | response | Additive mirror of the three above, with `RateLimit-Reset` as seconds. |\n| `Retry-After` | response | On every `429`. Nothing sets it on a `503`. `meta.retry_after` appears only on a lockout. |\n| `X-Request-Id` | response | A plain UUID v4, present on every response and echoed in error bodies as `request_id`. Send your own — as `X-Request-Id` or the lowercase `request_id` — and it is used instead, so your trace and ours share an id. |\n\nRate limits are per minute and the bucket is chosen by the request, not configured per route.\nAn integration — an API key or an OAuth token — sees three of them: **120** general, **10**\nfor creates and mutations, and **50** unauthenticated. The create bucket is charged even when\nthe create fails, so a run of `422`s exhausts it.\n\nTwo details of that split are easy to get wrong. **`DELETE` is charged to the general bucket**,\nnot the create one: the tight bucket exists to slow document creation down, and a client\ncleaning up after a failed batch should not exhaust its create budget doing so. And **`POST\n/files` has its own `upload` bucket of 120**, because each file in a batch is a separate\nrequest and uploads must not spend the document-creation allowance before a draft can be made.\n\nA browser session is metered separately, so ordinary app usage never competes with an\nintegration's allowance: **300** for general app requests, **10** for unsafe `/security/*`\ncalls, and **60** for `POST /activity` telemetry. Sign-in itself has its own named limiters.\n\n## Status on a create response\n\nA create or send response carries a **transient** status, not the settled one. `POST\n/documents` and `POST /document_templates` always answer `Created`, whatever `draft` was;\n`POST /documents/{id}/send` and `POST /document_templates/documents` always answer `Draft`.\nThe real status appears on subsequent `GET`s. An emailed document may remain `Sending`\nwhile its provider hand-off is pending before it becomes `Sent`; drafts and templates\nsettle to `Draft` or `Available`. Do not drive a state machine off the create response.\n\n## Authentication\n\n- `ApiKey` — `X-Api-Key: <token>`. The default for server-to-server use.\n- `OAuth2` — authorization code with PKCE, or client credentials, on the API's own origin.\n  Discover the endpoints at `/.well-known/oauth-authorization-server`. A token is scoped to one\n  workspace and one audience.\n- `SignerSession` — a short-lived bearer token held by the signer app. It is the only scheme\n  accepted by the `Signer` operations and is never accepted anywhere else.\n- `Session` — the browser session Signclad's own web app holds. Not for integrations, but\n  listed on every operation that accepts it, because a large part of this surface acts on a\n  person and refuses an API key.\n\nEvery operation carries its own `security` block. Read it rather than assuming the document's\ndefault: the scope an OAuth token needs varies by family — `documents:*`, `templates:*`,\n`webhooks:*`, `account:read` — and a good deal of the surface is reachable by **no** scope at\nall, which is why those operations list no `OAuth2` alternative.\n",
    "contact": {
      "name": "Signclad API support",
      "url": "https://signclad.com/support",
      "email": "api@signclad.com"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://signclad.com/terms"
    },
    "termsOfService": "https://signclad.com/terms"
  },
  "externalDocs": {
    "description": "Signclad developer documentation",
    "url": "https://developers.signclad.com"
  },
  "servers": [
    {
      "url": "https://api.signclad.com/api/v1",
      "description": "Production. Sandbox is a property of the key (`sk_test_`), not a separate host."
    }
  ],
  "security": [
    {
      "ApiKey": []
    }
  ],
  "tags": [
    {
      "name": "Documents",
      "description": "Create, send, inspect, and download documents."
    },
    {
      "name": "Templates",
      "description": "Reusable documents with placeholders instead of recipients."
    },
    {
      "name": "Bulk sends",
      "description": "Send one or more templates to many recipients from a CSV."
    },
    {
      "name": "Hooks",
      "description": "Compatibility webhook registration. One callback URL receives every event."
    },
    {
      "name": "API applications",
      "description": "Embedded branding and callback configuration for partner applications."
    },
    {
      "name": "Account",
      "description": "The authenticated membership, its user, account, and workspace."
    },
    {
      "name": "Extensions",
      "description": "Additive Signclad operations. Not part of the compatibility surface; every one of them is\nmarked `x-signclad-extension: true` and is safe to ignore.\n"
    },
    {
      "name": "Files",
      "description": "Upload a file once and reference it by id everywhere a file is accepted."
    },
    {
      "name": "Accounts",
      "description": "Workspaces, members and invitations, API keys, billing, and the workspace audit log. Every\noperation here is an extension; no compatibility client calls any of it.\n"
    },
    {
      "name": "Security",
      "description": "The authenticated half of identity: MFA enrolment, passkeys, recovery codes, the session\nlist, and the security event log. An API key authenticates but is refused — a key is a\ncredential, not a person.\n"
    },
    {
      "name": "Authentication",
      "description": "Sign-in itself: register, log in, log out, Google, magic links, password reset, email\nverification. These set and clear a session cookie, live on the API root rather than under\n`/api/v1`, and are not part of the versioned contract.\n"
    },
    {
      "name": "OAuth",
      "description": "The OAuth 2.1 authorization server and the SPA's view of it. The protocol endpoints live on\nthe API root, unversioned, because the RFCs are their contract and the metadata document\nadvertises them there.\n"
    },
    {
      "name": "Branding",
      "description": "Workspace branding and API application branding — the same three layers, two surfaces."
    },
    {
      "name": "Template links",
      "description": "The public fill link, and the settings behind it."
    },
    {
      "name": "Admin",
      "description": "Staff-only. Every route answers `404` to everyone else, so the existence of the surface is\nnever confirmed to a stranger.\n"
    },
    {
      "name": "Signer",
      "description": "The API the signer app (`app.signclad.com/sign/...`) calls with a signer session bearer token.\nDocumented for completeness; it is not part of the public integration surface and is not\nreachable with an API key.\n"
    }
  ],
  "paths": {
    "/documents": {
      "post": {
        "tags": [
          "Documents"
        ],
        "operationId": "createDocument",
        "summary": "Create a document",
        "description": "Creates a document from one or more files and sends it for signature. `draft: true`\nkeeps it editable and returns a document-level `embedded_edit_url` instead of sending.\n\nReturns `201` immediately with the **transient** status `Created`, whatever `draft`\nwas; subsequent `GET`s report `Draft`, or `Sending` followed by `Sent` once the email\nprovider accepts the initial invitation.\n`files[].pages_number` is `0` in this response and carries the real count on the next\n`GET`; the extension field `files[].status` is `processing` until ingest finishes.\nSigning and embedded URLs are valid straight away.\n\nUnknown properties are dropped silently: they are neither rejected nor echoed back.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/SourceHeader"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentCreateRequest"
              },
              "examples": {
                "simple": {
                  "$ref": "#/components/examples/DocumentCreateSimple"
                },
                "embeddedDraft": {
                  "$ref": "#/components/examples/DocumentCreateEmbeddedDraft"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                },
                "examples": {
                  "created": {
                    "$ref": "#/components/examples/DocumentCreated"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "get": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "listDocuments",
        "summary": "List documents",
        "description": "Cursor-paginated list of the workspace's documents, newest first. Replaces polling\ndocuments one id at a time.\n\nThis list sends `total_count`: a second count over the same filters, so tab badges\ndo not need a page walk.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FilterStatus"
          },
          {
            "$ref": "#/components/parameters/FilterLabel"
          },
          {
            "$ref": "#/components/parameters/FilterRecipientEmail"
          },
          {
            "$ref": "#/components/parameters/FilterUpdatedSince"
          },
          {
            "$ref": "#/components/parameters/FilterMetadata"
          },
          {
            "$ref": "#/components/parameters/FilterQuery"
          },
          {
            "$ref": "#/components/parameters/FilterArchived"
          },
          {
            "$ref": "#/components/parameters/FilterArchivedOnly"
          },
          {
            "$ref": "#/components/parameters/FilterTestMode"
          },
          {
            "$ref": "#/components/parameters/SortBy"
          },
          {
            "$ref": "#/components/parameters/SortDirection"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/CursorLimit"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of documents.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "get": {
        "tags": [
          "Documents"
        ],
        "operationId": "getDocument",
        "summary": "Get a document",
        "description": "The full document object, including recipients, fields, and files. This is where the\nsettled status lives: a document that answered `Created` at create reads `Draft` or\n`Sent` here.\n\n`fields` comes back grouped by field type, not in the order it was sent. Address fields\nby `api_id`, never by index.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                },
                "examples": {
                  "sent": {
                    "$ref": "#/components/examples/DocumentSent"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "updateDocument",
        "summary": "Update a draft document",
        "description": "Updates settings, recipients, and fields on a document that is still a draft. Sending a\ndraft is `POST /documents/{id}/send`; this operation never sends.\n\nReturns `409 document_not_active` when the document has left the draft state — **except**\nfor `archived`. A body that carries nothing but `archived` is applied to a document in any\nstate, including a completed or canceled one, and writes no revision. Archiving is\nfiling, not editing.\n\nA sent document is not frozen, though: a recipient who has not started can still be\ncorrected through `PATCH /documents/{id}/recipients` while the document is `Sent`,\n`Viewed` or `Pending`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated draft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Documents"
        ],
        "operationId": "deleteDocument",
        "summary": "Delete a document",
        "description": "Deletes the document and cancels signing that is in progress.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/send": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "post": {
        "tags": [
          "Documents"
        ],
        "operationId": "sendDocument",
        "summary": "Update and send a draft",
        "description": "Sends a draft document. Accepts most create-time settings, but not `files`,\n`recipients`, or `fields`; edit those through the builder or `PATCH /documents/{id}`\nbefore sending.\n\nThe response carries the transient status `Draft`; subsequent `GET`s report `Sending`\nuntil the email provider accepts the initial invitation, then `Sent`. Sending also\nregenerates every recipient's `signing_url`, so a URL captured from the draft response\nis dead once this call returns.\n\nReturns `409 document_not_active` when the document is not a draft, and applies none of\nthe settings in the body when it does: the state is checked before anything is written.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentSendRequest"
              },
              "examples": {
                "withMessage": {
                  "summary": "Send with an overridden subject and message",
                  "value": {
                    "subject": "Your loan documents are ready",
                    "message": "<p>Please review and sign.</p>",
                    "reminders": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/remind": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "post": {
        "tags": [
          "Documents"
        ],
        "operationId": "remindDocument",
        "summary": "Send a reminder",
        "description": "Reminds recipients who have not finished. Omitting `recipients` reminds every unsigned\nrecipient.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentRemindRequest"
              },
              "examples": {
                "one": {
                  "summary": "Remind one recipient",
                  "value": {
                    "recipients": [
                      {
                        "email": "jane@example.com",
                        "name": "Jane Doe"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Reminder queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/recipients": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "patch": {
        "tags": [
          "Documents"
        ],
        "operationId": "updateDocumentRecipients",
        "summary": "Update recipients",
        "description": "Corrects recipient name and email on a live document. Allowed only while the document is\n`Sent`, `Viewed` or `Pending`, and only for recipients who have not started signing — a\nrecipient who has saved a value or signed is past correcting. Any other state is\n`409 document_not_active`.\n\nThis is how a typo in an address is fixed after sending, and the reason `PATCH\n/documents/{id}` refusing a non-draft is not the end of the story.\n\n`delivery_method` and `phone_number` cannot be changed here. Non-embedded recipients are\ne-mailed again; every updated recipient's old links stop working.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecipientsUpdateRequest"
              },
              "examples": {
                "fixTypo": {
                  "summary": "Fix a mistyped address",
                  "value": {
                    "recipients": [
                      {
                        "id": "signer-1",
                        "name": "Jane Doe",
                        "email": "jane.doe@example.com"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/authentication": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "patch": {
        "tags": [
          "Documents"
        ],
        "operationId": "updateDocumentAuthentication",
        "summary": "Update recipient authentication",
        "description": "Sets or clears the passcode and passcode delivery for individual recipients.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AuthenticationUpdateRequest"
              },
              "examples": {
                "setPasscode": {
                  "summary": "Add a passcode delivered by email",
                  "value": {
                    "recipients": [
                      {
                        "id": "signer-1",
                        "passcode": "482913",
                        "passcode_delivery": {
                          "enabled": true,
                          "methods": [
                            "email"
                          ],
                          "expire_after_access": true
                        }
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/completed_pdf": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "get": {
        "tags": [
          "Documents"
        ],
        "operationId": "getCompletedPdf",
        "summary": "Download the completed PDF",
        "description": "Returns the sealed PDF bytes, or a short-lived link when `url_only` is true. The bytes\ncome back as `application/pdf` with `content-disposition: inline`, filenamed from the\ndocument name plus each signer's name, lowercased and underscore-joined.\n\nReturns `404` (never `403`) until every signer has finished, so a retry loop can key on\nthe status code alone. That `404` body is byte-identical to the one for a document that\ndoes not exist, so only context distinguishes them.\n\n**Long-term validation is served by default.** A workspace whose plan includes `pades_lt`\ngets the `completed_lt` artifact — the PAdES B-LT/B-LTA enrichment — from this route with\nno parameter, whenever the document has one. There is no opt-in because a customer who\npaid for a file that still verifies in ten years should not have to change their\nintegration to receive it. The substitution is safe: the enrichment is an incremental\nupdate appended after the canonical file's `%%EOF`, so the canonical bytes are a **prefix**\nof what comes back, every signature over them still verifies, and the `completed`\nartifact's SHA-256 still describes the first `source_size` bytes. `GET /verify/{hash}`\nrecognises the enriched file's own hash. See `api/docs/ltv.md`.\n\n`audit_page=false` is unaffected: only the canonical artifact is enriched.\n\nOn a 21 CFR Part 11 document the audit page is part of the record: `audit_page=false` and\n`file_format=zip` answer `422` with `errors.audit_page` or `errors.file_format` keyed\n`not_available_on_cfr_part11` — the artifact asked for is not one this document may\nproduce, and a `404` would not say so.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "url_only",
            "in": "query",
            "required": false,
            "description": "Return a JSON object with a short-lived download URL instead of the bytes.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "audit_page",
            "in": "query",
            "required": false,
            "description": "Include the audit page. `false` returns the separately generated and sealed\nno-audit artifact. Cannot be disabled on 21 CFR Part 11 documents.\n",
            "schema": {
              "type": "boolean",
              "default": true
            }
          },
          {
            "name": "audit_only",
            "in": "query",
            "required": false,
            "description": "Return the signed `audit.json` — the event chain, the chain head, the revision\nmanifest hash, and the hashes of the PDF artifacts — instead of a PDF.\n",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "x-signclad-extension": true
          },
          {
            "name": "file_format",
            "in": "query",
            "required": false,
            "description": "`pdf` returns one PDF. `zip` returns the sealed PDF, per-file variants, requested\nattachments, and the signed `audit.json`.\n",
            "schema": {
              "type": "string",
              "enum": [
                "pdf",
                "zip"
              ],
              "default": "pdf"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The completed file, or its URL when `url_only=true`.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompletedPdfUrl"
                },
                "examples": {
                  "urlOnly": {
                    "summary": "url_only=true",
                    "value": {
                      "completed_pdf_url": "https://files.signclad.com/signed/8f14e45f-ea8f-4b2c-9f0e-1b2c3d4e5f60.pdf?expires=1789050000&signature=...",
                      "audit_trail_url": "https://files.signclad.com/audit/8f14e45f-ea8f-4b2c-9f0e-1b2c3d4e5f60.json?expires=1789050000&signature=...",
                      "expires_at": "2026-09-09T18:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "The document does not exist for this workspace, or it is not complete yet. Both\ncases share this body.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                },
                "examples": {
                  "notComplete": {
                    "summary": "Byte-identical to the 404 for a document that does not exist",
                    "value": {
                      "message": "Not found",
                      "meta": {
                        "error": "record_not_found",
                        "message": "Couldn't find the document requested",
                        "messages": [
                          "Couldn't find the document requested"
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The artifact asked for cannot exist for this document — `errors.audit_page` or\n`errors.file_format` keyed `not_available_on_cfr_part11` on a Part 11 document.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                },
                "examples": {
                  "part11": {
                    "value": {
                      "errors": {
                        "audit_page": {
                          "not_available_on_cfr_part11": "The audit page is part of the record on a 21 CFR Part 11 document."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/completed/download": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "get": {
        "tags": [
          "Documents"
        ],
        "operationId": "downloadCompletedCopy",
        "summary": "Download the completed PDF from a signed email link",
        "description": "The completed copy, for whoever it was emailed to. **Unauthenticated** — the signature in\nthe query string is the whole credential, which is what makes it usable by a signer, who\nhas no Signclad account and no session.\n\nThe HMAC covers the document id, the `audience` and the `expires` value together, so a\nforwarded link cannot be re-aimed at another document, another recipient, or a later\nexpiry. It is minted by the completed-copy email and nowhere else; there is no endpoint\nthat issues one on request.\n\nOnly the canonical `completed` artifact is served. `file_format`, `audit_only`,\n`audit_page` and the long-term-validation substitution are all choices for\n`GET /documents/{id}/completed_pdf`, which takes a credential and can afford them.\n\nEvery failure is the same `404`, byte-identical to the one for a document that does not\nexist: a bad signature, a tampered `audience`, an expired link, an unsealed document and\nan unknown id are indistinguishable from outside.\n",
        "x-signclad-extension": true,
        "security": [],
        "parameters": [
          {
            "name": "audience",
            "in": "query",
            "required": true,
            "description": "Who the copy was addressed to — a recipient id, an email address, or `copy`. Covered by the signature.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "expires",
            "in": "query",
            "required": true,
            "description": "Unix timestamp after which the link stops working. Covered by the signature.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "signature",
            "in": "query",
            "required": true,
            "description": "HMAC-SHA256 over the path, `audience` and `expires`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The sealed PDF bytes.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "Bad or expired signature, tampered `audience`, unknown document, or a document that is\nnot sealed. All of them share this body.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                },
                "examples": {
                  "anyFailure": {
                    "summary": "Byte-identical whatever went wrong",
                    "value": {
                      "message": "Not found",
                      "meta": {
                        "error": "record_not_found",
                        "message": "Couldn't find the document requested",
                        "messages": [
                          "Couldn't find the document requested"
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/nom151_certificate": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "get": {
        "tags": [
          "Documents"
        ],
        "operationId": "getNom151Certificate",
        "summary": "Download the NOM-151 constancia",
        "description": "Mexico NOM-151 constancia de conservación for a completed document, issued by a\ncertification service provider after sealing.\n\nThree answers, from one `200`: the ZIP bytes by default, a link with `url_only`, or the\nparsed constancia with `object_only`. `url_only` wins if both are set.\n\nThe order of the refusals is the contract, and each means something different to act on. A\ndocument in another workspace is `404` before anything else. Then, in order:\n\n- `422 not_available_on_plan` — the plan does not carry NOM-151. Unchanged from before the\n  feature shipped, so a client written against that answer keeps working.\n- `422 not_enabled` — the plan carries it and the workspace has not switched it on. This\n  never becomes true by retrying; somebody has to change a setting.\n- `422 certificate_unavailable` — switched on, and the constancia is not issued **yet**. The\n  only one of the three worth retrying.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "url_only",
            "in": "query",
            "required": false,
            "description": "Return a JSON object with a short-lived download URL instead of the bytes.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "object_only",
            "in": "query",
            "required": false,
            "description": "Return the parsed constancia object instead of the file.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The constancia. A ZIP by default — it holds the certificate, the certified file, and the\nchain — a link with `url_only`, or the parsed object with `object_only`.\n",
            "content": {
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Nom151Certificate"
                    },
                    {
                      "type": "object",
                      "additionalProperties": true,
                      "properties": {
                        "file_url": {
                          "type": "string",
                          "format": "uri"
                        }
                      },
                      "required": [
                        "file_url"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "One of three, under `errors.base`: `not_available_on_plan` (with `meta.feature`),\n`not_enabled`, or `certificate_unavailable`. Only the last is worth retrying.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                },
                "examples": {
                  "notOnPlan": {
                    "value": {
                      "errors": {
                        "base": {
                          "not_available_on_plan": "This feature is not available on your plan."
                        }
                      },
                      "meta": {
                        "feature": "nom151"
                      }
                    }
                  },
                  "notEnabled": {
                    "value": {
                      "errors": {
                        "base": {
                          "not_enabled": "NOM-151 conservation is not enabled for this workspace. Turn it on in compliance settings."
                        }
                      }
                    }
                  },
                  "notIssuedYet": {
                    "value": {
                      "errors": {
                        "base": {
                          "certificate_unavailable": "A NOM-151 conservation certificate has not been issued for this document yet."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/events": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "listDocumentEvents",
        "summary": "List document events",
        "description": "The document's audit trail: every event with its sequence, actor, IP, user agent, and\nhash-chain link. The same rows that produce the audit page and `audit.json`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Events per page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Filter to one or more event types.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "style": "form",
            "explode": true
          }
        ],
        "responses": {
          "200": {
            "description": "A page of events, oldest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentEventList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/attachments": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "listDocumentAttachments",
        "summary": "List uploaded attachments",
        "description": "Files that recipients uploaded to satisfy `attachment_requests`.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The attachments.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Attachment"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/cancel": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "cancelDocument",
        "summary": "Cancel a document",
        "description": "Stops signing and moves the document to `Canceled` without deleting it, so the audit\ntrail survives. `DELETE /documents/{id}` cancels and deletes.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CancelRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The canceled document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/complete": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "completeDocument",
        "summary": "Mark a document manually completed",
        "description": "Ends signing early and seals what has been signed so far. The document's status becomes\n`Manually completed` and the reason is recorded in the audit trail.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompleteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The completed document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/recipients/{recipient_id}/reassign": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        },
        {
          "$ref": "#/components/parameters/RecipientId"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "reassignRecipient",
        "summary": "Reassign a recipient",
        "description": "Hands a recipient's turn to somebody else. The original recipient's links stop working,\na new recipient row is created with `reassigned_from`, and the reassignment is recorded\nin the audit trail. Allowed only while `allow_reassign` is true and the recipient has\nnot signed.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReassignRequest"
              },
              "examples": {
                "toColleague": {
                  "value": {
                    "name": "Sam Rivera",
                    "email": "sam@example.com",
                    "reason": "Jane is out of office"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new recipient.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recipient"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/recipients/{recipient_id}/signing_url": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        },
        {
          "$ref": "#/components/parameters/RecipientId"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "createRecipientSigningUrl",
        "summary": "Regenerate a signing URL",
        "description": "Issues a fresh single-use embedded signing URL for one recipient. This is the only\nregeneration route; there is no `GET`. Hosts call it instead of recreating the document\nevery time somebody opens their portal.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SigningUrlRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "A new signing URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SigningUrlResult"
                },
                "examples": {
                  "created": {
                    "value": {
                      "embedded_signing_url": "https://app.signclad.com/sign/embedded/4f8c1b7a2e9d05364c8b1e7a2d9f06354b8c1e7a2d9f06354b8c1e7a2d9f0635",
                      "signing_url": "https://app.signclad.com/sign/4f8c1b7a2e9d05364c8b1e7a2d9f06354b8c1e7a2d9f06354b8c1e7a2d9f0635",
                      "expires_at": "2026-09-09T18:15:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/recipients/{recipient_id}/in_person_session": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        },
        {
          "$ref": "#/components/parameters/RecipientId"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "createInPersonSession",
        "summary": "Start an in-person signing session",
        "description": "Signing in person: the host is standing next to the signer with a tablet and hands it over.\nNo email, no link, nothing to click — which is the point, because the situations this is\nfor are the ones where the signer has no working email in the room.\n\nA write on the **sender's** credential. What comes back is a signer bearer token scoped to\nexactly one recipient, with the same lifetime every other signer session has, so the tablet\ncan be handed over without exposing the sender's own session. Present it as\n`Authorization: Bearer <token>` against `/signer/*`, exactly as a token from\n`POST /signer/exchange`.\n\nThe guards are the link exchange's, minus the two that are about a link: the document has\nto be open, the recipient has to be a signer who has not finished (`409\nrecipient_not_a_signer` / `recipient_not_signable`), and the signing order has to have\nreached them (`403 not_your_turn`). The passcode gate is deliberately **not** applied — a\npasscode proves that the person who received the email is the one opening it, and here the\nhost has identified the signer face to face.\n\nThe session is recorded as a `signer_session_started` event with `source: in_person`, and a\ndocument whose `source` is `web` is promoted to `in_person`. A document created through the\nAPI, a bulk send or a template link keeps the provenance it was created with, so the column\nnever disagrees with the usage row written when it was sent.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "201": {
            "description": "A signer session for the host's device.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InPersonSession"
                },
                "examples": {
                  "created": {
                    "value": {
                      "token": "sgn_sess_9c8f2b1a4d6e5f70819a2b3c4d5e6f70",
                      "token_type": "Bearer",
                      "expires_at": "2026-09-09T18:15:00Z",
                      "scope": "in_person",
                      "document_id": "8f14e45f-ea8f-4b2c-9f0e-1b2c3d4e5f60",
                      "recipient_id": "recipient_1",
                      "session_id": "01J9K3P2S5N7Q8R0T2V4X6Z8A0"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/PlanRestricted"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/embedded_edit_url": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "createDocumentEmbeddedEditUrl",
        "summary": "Regenerate an embedded builder URL",
        "description": "Issues a fresh single-use `embedded_edit_url` for the document builder. The embed script\nnormally receives a renewal URL from the iframe in its `setId` message; this route is\nfor hosts that lost it.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmbeddedEditUrlRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "A new builder URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmbeddedEditUrlResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/documents/{id}/files/{position}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        },
        {
          "$ref": "#/components/parameters/FilePosition"
        }
      ],
      "put": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "replaceDocumentFile",
        "summary": "Replace a file on a draft",
        "description": "Swaps one file of a draft document and re-anchors its fields. Run it once with\n`dry_run: true` to see the report, then again with `dry_run: false` and the\n`expected_revision` the dry run returned.\n\n`PUT /document_templates/{id}/files/{position}` is the template equivalent and behaves\nidentically, against template revisions instead of draft revisions.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FileReplaceRequest"
              },
              "examples": {
                "dryRun": {
                  "$ref": "#/components/examples/FileReplaceDryRun"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The replacement report. Nothing changed when `dry_run` was true.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileReplaceResult"
                },
                "examples": {
                  "report": {
                    "$ref": "#/components/examples/FileReplaceReportExample"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/document_templates": {
      "post": {
        "tags": [
          "Templates"
        ],
        "operationId": "createTemplate",
        "summary": "Create a template",
        "description": "Same shape as a document, with `placeholders` instead of `recipients` and\n`placeholder_id` on fields. `draft: false` publishes the template; `draft: true` returns\nan `embedded_edit_url` for the template builder.\n\nThe response carries the transient status `Created` in both cases. The settled status\n(`Draft` or `Available`) appears on the next `GET`, and `PUT {draft: false}` moves a\ndraft to `Available`.\n\n`fields` is accepted both as an array of arrays (one inner array per file) and as an\nobject keyed by file and field, for compatibility with clients that build it as a map.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/SourceHeader"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TemplateCreateRequest"
              },
              "examples": {
                "draftBuilder": {
                  "$ref": "#/components/examples/TemplateCreateDraft"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                },
                "examples": {
                  "created": {
                    "$ref": "#/components/examples/TemplateCreated"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "get": {
        "tags": [
          "Extensions",
          "Templates"
        ],
        "operationId": "listTemplates",
        "summary": "List templates",
        "description": "Cursor-paginated list of the workspace's templates. `total_count` counts every template\nmatching the filters so navigation badges do not need to fetch and serialize every row.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "templates:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FilterTemplateStatus"
          },
          {
            "$ref": "#/components/parameters/FilterLabel"
          },
          {
            "$ref": "#/components/parameters/FilterUpdatedSince"
          },
          {
            "$ref": "#/components/parameters/FilterMetadata"
          },
          {
            "$ref": "#/components/parameters/FilterQuery"
          },
          {
            "$ref": "#/components/parameters/FilterArchived"
          },
          {
            "$ref": "#/components/parameters/FilterTestMode"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/CursorLimit"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of templates.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/document_templates/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/TemplateId"
        }
      ],
      "get": {
        "tags": [
          "Templates"
        ],
        "operationId": "getTemplate",
        "summary": "Get a template",
        "description": "The full template, including `placeholders`, `copied_placeholders`, `template_link`\n(the public fill link, which works even while the template is still a draft) and a\nsingle-use `embedded_edit_url`. `fields` is an array of arrays, one per file, and each\nfield carries `placeholder_name` — never `placeholder_id`.\n\nThis is where a template's settled status lives; a create answered `Created`.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "templates:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The template.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                },
                "examples": {
                  "available": {
                    "$ref": "#/components/examples/TemplateAvailable"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Templates"
        ],
        "operationId": "saveTemplate",
        "summary": "Save a template editor snapshot",
        "description": "Saves fields and roles atomically with settings and a revision. Fields replace the entire\nfield collection and address roles by `placeholder_name` or `placeholder_id`, and every\nplaceholder must carry its `id` — this is a snapshot of an editor that already knows them,\nnot a create. `draft: true` returns the template to Draft; `false` publishes it.\n\n`files` is a **replacement of the whole ordered set**, by `file_id`, and files dropped from\nit are scheduled for deletion. It takes no new sources: upload through `POST /files` first.\nOmit the key to leave the files alone.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/TemplateUpdateRequest"
                  },
                  {
                    "type": "object",
                    "required": [
                      "placeholders",
                      "fields"
                    ],
                    "properties": {
                      "placeholders": {
                        "type": "array",
                        "minItems": 1,
                        "items": {
                          "allOf": [
                            {
                              "$ref": "#/components/schemas/Placeholder"
                            },
                            {
                              "type": "object",
                              "required": [
                                "id"
                              ]
                            }
                          ]
                        }
                      },
                      "fields": {
                        "type": "array",
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Field"
                          }
                        }
                      },
                      "files": {
                        "type": "array",
                        "maxItems": 20,
                        "description": "The ordered file set, replaced wholesale. Files left out are scheduled for\ndeletion. Omit the key to leave the set alone.\n",
                        "items": {
                          "type": "object",
                          "additionalProperties": true,
                          "properties": {
                            "file_id": {
                              "type": "string"
                            },
                            "name": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "maxLength": 255
                            }
                          },
                          "required": [
                            "file_id"
                          ]
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved template.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "put": {
        "tags": [
          "Templates"
        ],
        "operationId": "updateTemplate",
        "summary": "Update template settings",
        "description": "Settings only; files, placeholders, and fields are edited in the builder.\n\n`labels` and `checkbox_groups` are replaced wholesale by what the request carries.\n`conditional_rules` are upserted by `id`; send `{\"id\": \"...\", \"deleted\": true}` to\nremove one.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TemplateUpdateRequest"
              },
              "examples": {
                "settings": {
                  "summary": "Rename and re-label",
                  "value": {
                    "name": "Loan agreement (2026)",
                    "subject": "Your loan agreement",
                    "expires_in": 30,
                    "labels": [
                      "loans",
                      "onboarding"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated template.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Templates"
        ],
        "operationId": "deleteTemplate",
        "summary": "Delete a template",
        "description": "Documents already created from the template are unaffected.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/document_templates/documents": {
      "post": {
        "tags": [
          "Templates"
        ],
        "operationId": "createDocumentFromTemplate",
        "summary": "Create a document from templates",
        "description": "Creates a document from one template (`template_id`) or several composed in order\n(`template_ids`). Exactly one of the two is required.\n\nRecipients are assigned to placeholders by `placeholder_name` and inherit that\nplaceholder's fields and settings. `files` and `fields` are **appended**: they never\nreplace the template's files, and each inner `fields` array belongs to one appended\nfile.\n\nSignatures and initials already applied on the template are copied in as content: they\nstay visible, create no signing event, fire no `document_signed`, and do not change any\nrecipient's status. If the request adds new unsigned signature or initials fields for\nthe same signer, the pre-applied ones are cleared so that signer signs once.\n\nThe response carries the transient status `Draft` whatever `draft` was; the settled\nstatus appears on the next `GET`.\n\nOn a composed document a field's identity is the pair (source template, `api_id`).\nNothing is renamed, every field in the response carries `template_id`, and rule and\ngroup references resolve inside their source template. A `template_fields` entry may be\n`{api_id, value}` when the `api_id` is unique across the templates, and must be\n`{template_id, api_id, value}` when it is not; an ambiguous bare reference is `422`\n`ambiguous_field_reference` and lists the candidates.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/SourceHeader"
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TemplateDocumentCreateRequest"
              },
              "examples": {
                "fromOneTemplate": {
                  "$ref": "#/components/examples/TemplateDocumentCreate"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "Validation failed. A single bad `template_fields` entry rejects the whole request\nwith `errors.template_fields`, unless `skip_invalid_fields: true` was sent, in\nwhich case the bad entries are dropped and listed in `warnings[]`. A draft or\narchived template cannot spawn a document.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                },
                "examples": {
                  "badTemplateField": {
                    "summary": "A bare Y-m-d date, keyed by the field's api_id then by the error code",
                    "value": {
                      "errors": {
                        "template_fields": {
                          "tdate1": {
                            "invalid_date_format": "DateField value must be in Iso8601 format."
                          }
                        }
                      }
                    }
                  },
                  "unknownTemplate": {
                    "summary": "An unknown template id and an unmatched placeholder, reported together",
                    "value": {
                      "errors": {
                        "template_id": {
                          "not_in_account": "There's no template with id 00000000-0000-0000-0000-000000000000 in your account."
                        },
                        "recipients": {
                          "recipient_1": {
                            "unexisting_placeholder_name": "This placeholder_name is not present in the template(s): signer."
                          }
                        }
                      }
                    }
                  },
                  "templateNotFinished": {
                    "summary": "A draft or archived template cannot spawn a document",
                    "value": {
                      "errors": {
                        "template_id": {
                          "not_finished": "Template 3f1c9a2e is present in your account but is not finished."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/document_templates/{id}/duplicate": {
      "parameters": [
        {
          "$ref": "#/components/parameters/TemplateId"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Templates"
        ],
        "operationId": "duplicateTemplate",
        "summary": "Duplicate a template",
        "description": "Copies files, placeholders, fields, checkbox groups, conditional rules, and settings\ninto a new template. Field `api_id`s are preserved, so field mappings keep working.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DuplicateTemplateRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new template.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/document_templates/{id}/embedded_edit_url": {
      "parameters": [
        {
          "$ref": "#/components/parameters/TemplateId"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Templates"
        ],
        "operationId": "createTemplateEmbeddedEditUrl",
        "summary": "Regenerate a template builder URL",
        "description": "Issues a fresh single-use `embedded_edit_url` for the template builder.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmbeddedEditUrlRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "A new builder URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmbeddedEditUrlResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/document_templates/{id}/files/{position}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/TemplateId"
        },
        {
          "$ref": "#/components/parameters/FilePosition"
        }
      ],
      "put": {
        "tags": [
          "Extensions",
          "Templates"
        ],
        "operationId": "replaceTemplateFile",
        "summary": "Replace a file on a template",
        "description": "Swaps one file of a template and re-anchors its fields, producing a new template\nrevision. Dry run first, then commit with the `expected_revision` from the dry run;\na template that changed in between fails with `409 revision_mismatch`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FileReplaceRequest"
              },
              "examples": {
                "commit": {
                  "summary": "Commit against a known revision",
                  "value": {
                    "file_id": "file_01J9K3P2S5N7Q8R1T2V3W4X5",
                    "strategy": "anchors",
                    "scale_to_page": true,
                    "dry_run": false,
                    "expected_revision": 7
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The replacement report.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileReplaceResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/files": {
      "get": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "listStoredFiles",
        "summary": "List reusable stored files",
        "description": "Lists ready original files in the current workspace. Members see files in\naccessible documents and templates; owners, admins, and workspace credentials\nalso see unattached uploads and retained history. Sandbox restrictions apply.\nFiles are returned once, newest first, in pages of 20. No storage is copied.\n`can_delete` is false while any live reference or saved history needs the file.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000,
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of reusable files",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "has_more"
                  ],
                  "properties": {
                    "has_more": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/UploadedFile"
                          },
                          {
                            "type": "object",
                            "required": [
                              "can_delete",
                              "delete_blocked_reason"
                            ],
                            "properties": {
                              "can_delete": {
                                "type": "boolean"
                              },
                              "delete_blocked_reason": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            }
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "uploadFile",
        "summary": "Upload a file",
        "description": "Uploads a file once and returns an id usable as `files[].file_id` anywhere `file_url`\nor `file_base64` is accepted. 25 MB per file. The response `status` is `processing`\nuntil ingest finishes; a file may be referenced before it is `ready`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The file bytes."
                  },
                  "name": {
                    "type": "string",
                    "description": "Display name. Defaults to the uploaded filename."
                  },
                  "purpose": {
                    "type": "string",
                    "enum": [
                      "document",
                      "attachment",
                      "import"
                    ],
                    "default": "document"
                  }
                },
                "required": [
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The stored file.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadedFile"
                },
                "examples": {
                  "created": {
                    "value": {
                      "id": "file_01J9K3P2S5N7Q8R1T2V3W4X5",
                      "name": "loan-agreement.pdf",
                      "mime": "application/pdf",
                      "size": 184320,
                      "status": "processing",
                      "pages_number": 0,
                      "created_at": "2026-09-09T17:44:10Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/bulk_sends": {
      "post": {
        "tags": [
          "Bulk sends"
        ],
        "operationId": "createBulkSend",
        "summary": "Start a bulk send",
        "description": "Sends one or more templates to every row of a CSV. The CSV columns are\n`<placeholder_name>_<field_label>`, plus a name and email column per placeholder; fetch\na matching header row from `GET /bulk_sends/csv_template`.\n\nThe response status starts at `Enqueuing` while rows are turned into documents.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkSendCreateRequest"
              },
              "examples": {
                "fromCsv": {
                  "summary": "One template, base64 CSV",
                  "value": {
                    "name": "Q3 onboarding",
                    "template_ids": [
                      "3f1c9a2e-6b7d-4c8e-9a0b-1c2d3e4f5a6b"
                    ],
                    "bulk_send_csv": "U2lnbmVyX05hbWUsU2lnbmVyX0VtYWlsCkphbmUgRG9lLGphbmVAZXhhbXBsZS5jb20K",
                    "skip_row_errors": false,
                    "subject": "Please sign your onboarding packet",
                    "message": "<p>This takes about two minutes.</p>",
                    "apply_signing_order": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The bulk send.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkSend"
                },
                "examples": {
                  "enqueuing": {
                    "$ref": "#/components/examples/BulkSendEnqueuing"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "get": {
        "tags": [
          "Bulk sends"
        ],
        "operationId": "listBulkSends",
        "summary": "List bulk sends",
        "description": "Every bulk send in the workspace, newest first, up to 200. The body is a **bare array**,\nnot a `data` envelope, and takes no pagination parameters.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The bulk sends.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "maxItems": 200,
                  "items": {
                    "$ref": "#/components/schemas/BulkSend"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/PlanRestricted"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/bulk_sends/csv_template": {
      "get": {
        "tags": [
          "Bulk sends"
        ],
        "operationId": "getBulkSendCsvTemplate",
        "summary": "Get a CSV template",
        "description": "Returns the header row for the given templates: a `<Placeholder>_Name` and\n`<Placeholder>_Email` column per placeholder, then one `<Placeholder>_<field label>`\ncolumn per fillable field. Signature, initials, `autofill_*`, and locked date fields get\nno column — nothing in the CSV could fill them.\n\nThe CSV form carries a UTF-8 byte-order mark so spreadsheet software opens it correctly;\nthe base64 form does not.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "template_ids[]",
            "in": "query",
            "required": true,
            "description": "One or more template ids, in the order they will be composed.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              },
              "minItems": 1
            },
            "style": "form",
            "explode": true
          },
          {
            "name": "base64",
            "in": "query",
            "required": false,
            "description": "Return the CSV base64-encoded inside a JSON object instead of raw text.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The CSV template.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "examples": {
                  "headerRow": {
                    "value": "Signer_Name,Signer_Email,Signer_Full name,Signer_Start date\n"
                  }
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkSendCsvTemplate"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "description": "There is no id in the path, so a template that does not exist, is not in this\nworkspace, or is not finished is a validation failure rather than a `404`:\n`errors['template_ids.not_found']` and `errors['template_ids.not_finished']` name the\nid. A plan without bulk send answers here too, as `errors.base.not_available_on_plan`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/bulk_sends/validate_csv": {
      "post": {
        "tags": [
          "Bulk sends"
        ],
        "operationId": "validateBulkSendCsv",
        "summary": "Validate a bulk send CSV",
        "description": "Dry run. Parses the CSV against the templates and reports per-row errors without\ncreating anything.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkSendValidateCsvRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The validation result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkSendValidationResult"
                },
                "examples": {
                  "oneBadRow": {
                    "value": {
                      "valid": false,
                      "rows_count": 42,
                      "valid_rows_count": 41,
                      "row_errors": [
                        {
                          "row": 17,
                          "column": "Signer_Email",
                          "error": "is not a valid email address",
                          "errors": {
                            "Signer_Email": "is not a valid email address"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/bulk_sends/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/BulkSendId"
        }
      ],
      "get": {
        "tags": [
          "Bulk sends"
        ],
        "operationId": "getBulkSend",
        "summary": "Get a bulk send",
        "description": "Counts and status for one bulk send, plus the templates it used.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The bulk send.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkSend"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/PlanRestricted"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/bulk_sends/{id}/documents": {
      "parameters": [
        {
          "$ref": "#/components/parameters/BulkSendId"
        }
      ],
      "get": {
        "tags": [
          "Bulk sends"
        ],
        "operationId": "listBulkSendDocuments",
        "summary": "List a bulk send's documents",
        "description": "The documents a bulk send produced, page by page. This is the one paginated\ncompatibility endpoint and it uses page numbers, not cursors.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Documents per page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "1-based page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of documents.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkSendDocumentsPage"
                },
                "examples": {
                  "firstPage": {
                    "$ref": "#/components/examples/BulkSendDocumentsPageExample"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/PlanRestricted"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/hooks": {
      "get": {
        "tags": [
          "Hooks"
        ],
        "operationId": "listHooks",
        "summary": "List webhook callbacks",
        "description": "The workspace's compatibility callbacks. Endpoints created here receive every event and\nare active immediately, with no ping. Signclad's own webhook management, with per-event\nsubscriptions, secrets, health, and delivery logs, is at `/webhook_endpoints`; the same\nendpoints appear in both listings.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The callbacks.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Hook"
                  }
                },
                "examples": {
                  "one": {
                    "value": [
                      {
                        "id": "6d2f8b31-7a4c-4e19-9c53-0f8a1b2c3d4e",
                        "callback_url": "https://app.example.com/webhooks/signclad"
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "tags": [
          "Hooks"
        ],
        "operationId": "createHook",
        "summary": "Create a webhook callback",
        "description": "Registers a callback URL. Every event is delivered to it. The `id` is also the key of\nthe compatibility `event.hash`, so store it.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HookCreateRequest"
              },
              "examples": {
                "global": {
                  "value": {
                    "callback_url": "https://app.example.com/webhooks/signclad"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The callback.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Hook"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/hooks/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/HookId"
        }
      ],
      "delete": {
        "tags": [
          "Hooks"
        ],
        "operationId": "deleteHook",
        "summary": "Delete a webhook callback",
        "description": "Stops delivery immediately. Undelivered events are dropped.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api_applications/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiApplicationId"
        }
      ],
      "get": {
        "tags": [
          "API applications"
        ],
        "operationId": "getApiApplication",
        "summary": "Get an API application",
        "description": "Branding and callback configuration for an embedded partner application.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The API application.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiApplication"
                },
                "examples": {
                  "app": {
                    "$ref": "#/components/examples/ApiApplicationExample"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Extensions",
          "API applications"
        ],
        "operationId": "updateApiApplication",
        "summary": "Update an API application",
        "description": "Only the keys present change; a key present and `null` clears it. Colours are hex, origins\nare scheme-and-host, and `default_webhook_id` must name an endpoint in this workspace.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApiApplicationWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated application.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiApplication"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      },
      "delete": {
        "tags": [
          "API applications"
        ],
        "operationId": "deleteApiApplication",
        "summary": "Delete an API application",
        "description": "API applications are created in the web app; there is no create endpoint. Deleting one\ndetaches it from its documents and stops its embedded pages from loading.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/me": {
      "patch": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "updateMe",
        "summary": "Update your profile",
        "description": "Name, locale, and timezone. The address is not here — changing where sign-in links go goes\nthrough `POST /me/email` and needs the password.\n\n`name` and the two halves are kept consistent by one resolver: send `first_name` or\n`last_name` and they win, with `name` recomposed; send only `name` and the halves are\nre-derived by splitting on the first space. People only.\n\nReturns the same body as `GET /me`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "first_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "last_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "locale": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 12,
                    "description": "A language tag such as `en` or `en-GB`."
                  },
                  "timezone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 64,
                    "description": "An IANA name such as `America/Denver`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The membership, as `GET /me` returns it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      },
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getMe",
        "summary": "Get the current membership",
        "description": "The membership the credential resolves to, with its user, account, workspace, and\ncontact. Use it to check which workspace an API key belongs to.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The membership.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                },
                "examples": {
                  "me": {
                    "$ref": "#/components/examples/MeExample"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhook_endpoints": {
      "get": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "listWebhookEndpoints",
        "summary": "List webhook endpoints",
        "description": "Every endpoint in the workspace, including those registered through the compatibility\n`POST /hooks` route, with subscriptions and current health.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/CursorLimit"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "createWebhookEndpoint",
        "summary": "Create a webhook endpoint",
        "description": "Creates an endpoint with its own signing secret and event subscriptions. The `secret` is\nreturned once, on creation and on rotation, and never again.\n\n`verify: true` sends a `ping` first and activates the endpoint only if it answers `2xx`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointCreateRequest"
              },
              "examples": {
                "subscribed": {
                  "value": {
                    "url": "https://app.example.com/webhooks/signclad",
                    "description": "Production receiver",
                    "event_types": [
                      "document_completed",
                      "document_declined",
                      "document_error"
                    ],
                    "include_test_mode": false,
                    "verify": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The endpoint, with its secret shown once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointWithSecret"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhook_endpoints/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WebhookEndpointId"
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "getWebhookEndpoint",
        "summary": "Get a webhook endpoint",
        "description": "The endpoint with its subscriptions, health, and delivery metrics.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                },
                "examples": {
                  "healthy": {
                    "$ref": "#/components/examples/WebhookEndpointExample"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "updateWebhookEndpoint",
        "summary": "Update a webhook endpoint",
        "description": "Changes the URL, description, subscriptions, test-mode inclusion, or active state.\nPausing an endpoint keeps its events queued for replay; disabling drops them.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "put": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "replaceWebhookEndpoint",
        "summary": "Update a webhook endpoint (PUT alias)",
        "description": "The same operation as `PATCH`, accepted as `PUT` for clients whose HTTP layer cannot send\na `PATCH`. It is still a partial update: only the properties present are changed.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "deleteWebhookEndpoint",
        "summary": "Delete a webhook endpoint",
        "description": "Deletes the endpoint and its queued deliveries. Delivery logs are kept 30 days.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhook_endpoints/{id}/rotate_secret": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WebhookEndpointId"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "rotateWebhookEndpointSecret",
        "summary": "Rotate the signing secret",
        "description": "Issues a new secret and keeps the old one valid for an overlap window, so both signatures\nverify while the receiver is redeployed. Deliveries during the overlap carry two `v1`\nsignature values, one per secret.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "overlap_hours": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 168,
                    "default": 24,
                    "description": "How long the previous secret keeps signing alongside the new one."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new secret, shown once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointSecret"
                },
                "examples": {
                  "rotated": {
                    "value": {
                      "id": "6d2f8b31-7a4c-4e19-9c53-0f8a1b2c3d4e",
                      "secret": "whsec_9f8e7d6c5b4a39281706f5e4d3c2b1a0",
                      "previous_secret_expires_at": "2026-09-10T17:44:10Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhook_endpoints/{id}/health": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WebhookEndpointId"
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "getWebhookEndpointHealth",
        "summary": "Get endpoint health",
        "description": "Current health state and the counters behind it. `healthy` → `degraded` (3 consecutive\nfailures, or over 10% failures in an hour) → `failing` (25 consecutive failures, or 100%\nover 24 hours) → `disabled` (5 days of continuous failure). While `failing`, attempts\nare skipped and the endpoint is probed hourly.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The health record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointHealth"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhook_endpoints/{id}/ping": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WebhookEndpointId"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "pingWebhookEndpoint",
        "summary": "Send a test event",
        "description": "Delivers a synthetic event synchronously and returns what the endpoint answered. Pass\n`event_type` to send a realistic sample of that event instead of `ping`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "event_type": {
                    "$ref": "#/components/schemas/EventType"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The delivery result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookPingResult"
                },
                "examples": {
                  "ok": {
                    "value": {
                      "delivered": true,
                      "status_code": 200,
                      "latency_ms": 143,
                      "response_excerpt": "{\"ok\":true}",
                      "delivery_id": "01j9k3p2s5n7q8r1t2v3w4x5"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhook_endpoints/{id}/deliveries": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WebhookEndpointId"
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "listWebhookDeliveries",
        "summary": "List deliveries",
        "description": "The delivery log: every attempt with its request headers and body, response status and\nbody excerpt, and latency. Dead letters are kept 30 days.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by delivery outcome.",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "delivered",
                "failed",
                "dead"
              ]
            }
          },
          {
            "name": "event_type",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/EventType"
            }
          },
          {
            "name": "event",
            "in": "query",
            "required": false,
            "description": "Alias of `event_type`, accepted because that is what the field is called in the app.",
            "schema": {
              "$ref": "#/components/schemas/EventType"
            }
          },
          {
            "name": "document_id",
            "in": "query",
            "required": false,
            "description": "Only deliveries whose event belongs to this document.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/CursorLimit"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of deliveries. The request body, request headers, and response excerpt are\nomitted here; `GET .../deliveries/{delivery_id}` carries them.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeliveryList"
                },
                "examples": {
                  "failed": {
                    "$ref": "#/components/examples/WebhookDeliveryExample"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhook_endpoints/{id}/deliveries/{delivery_id}/replay": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WebhookEndpointId"
        },
        {
          "$ref": "#/components/parameters/DeliveryId"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "replayWebhookDelivery",
        "summary": "Replay a delivery",
        "description": "Re-sends the stored payload. `event.id`, `event.time`, and the body are unchanged, so\nreceivers that dedupe by `event.id` see a duplicate and drop it; the signature timestamp,\n`X-Signclad-Delivery`, and `X-Signclad-Attempt` are fresh.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "202": {
            "description": "The replay was queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDelivery"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/events": {
      "get": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "listEventCatalog",
        "summary": "Get the event catalog",
        "description": "Every event Signclad emits, what triggers it, and what its `data.object` carries, along\nwith the signature scheme and the retry schedule. Generated from the same table the\ndelivery pipeline validates subscriptions against, so the catalog cannot claim an event\nthat is not deliverable and cannot omit one that is.\n\nEvents a deployment has turned off are absent from the catalog, so subscribe from this\nlist rather than from a hard-coded one.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The catalog.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventCatalog"
                },
                "examples": {
                  "catalog": {
                    "value": {
                      "data": [
                        {
                          "type": "document_completed",
                          "summary": "Every signer has finished and the document is sealed.",
                          "object": "document",
                          "compatibility": true,
                          "carries_related_signer": false
                        }
                      ],
                      "signature": {
                        "header": "X-Signclad-Signature",
                        "scheme": "t=<unix>,v1=<hex hmac-sha256(secret, \"<t>.<raw body>\")>",
                        "tolerance_seconds": 300,
                        "compatibility_hash": "hex(hmac-sha256(key = <endpoint id>, data = \"<type>@<time>\"))"
                      },
                      "delivery": {
                        "timeout_seconds": 10,
                        "success": "Any 2xx.",
                        "retries": [
                          60,
                          300,
                          900,
                          3600,
                          10800,
                          28800,
                          86400,
                          172800
                        ],
                        "dedupe_on": "event.id",
                        "order_by": "event.sequence"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/ping": {
      "get": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "ping",
        "summary": "Liveness check",
        "description": "Unauthenticated liveness check for the API surface itself. It touches no tenancy and no\ndatabase, so a `200` here with a `401` elsewhere means the credential is the problem.\n",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "200": {
            "description": "The API is up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "api_version": {
                      "type": "string",
                      "examples": [
                        "v1"
                      ]
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/embed/context/{token}": {
      "parameters": [
        {
          "name": "token",
          "in": "path",
          "required": true,
          "description": "The signer or edit session's bearer token. It travels in the path because this is the\nfirst request the iframe document makes, before any script that could set a header has\nrun; the token is single-purpose, short-lived, and already in the page's own URL.\n",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Signer"
        ],
        "operationId": "getEmbedContext",
        "summary": "Bootstrap an embedded page",
        "description": "What an embedded page needs before it renders: which view to show, the API application's\nstyles, the `postMessage` vocabulary to speak, the parent origins allowed to frame it,\nand — for an edit session — the next single-use edit URL.\n\nThe response is served with `Content-Security-Policy: frame-ancestors` built from the\nsame allowlist it reports, defaulting to `'none'`.\n\nAn unknown, revoked, expired, or superseded token is a plain `404`: this route is\nreachable by anyone, so it never distinguishes the cases.\n",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "200": {
            "description": "The embed context.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmbedContext"
                },
                "examples": {
                  "builder": {
                    "$ref": "#/components/examples/EmbedContextExample"
                  }
                }
              }
            }
          },
          "404": {
            "description": "The token is unknown or no longer usable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signer/exchange": {
      "post": {
        "tags": [
          "Signer"
        ],
        "operationId": "exchangeSignerLink",
        "summary": "Exchange a link for a session",
        "description": "The only unauthenticated signer endpoint: the opaque token from a signing or embedded\nlink is the credential. Runs the gate — passcode, signing order — and returns a bearer\ntoken the signer app holds in memory and `sessionStorage`. No cookie is\nset, so it works inside a third-party iframe with cookies partitioned or blocked.\n\nCharged the 50/min unauthenticated bucket. An unknown token and a revoked one answer\nidentically, so spraying tokens at this route learns nothing per attempt.\n",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignerExchangeRequest"
              },
              "examples": {
                "withPasscode": {
                  "summary": "Second attempt, carrying the passcode the first attempt asked for",
                  "value": {
                    "token": "9c8f2b1a4d6e7f80a1b2c3d4e5f60718",
                    "passcode": "482913"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "A signer session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignerSessionToken"
                },
                "examples": {
                  "created": {
                    "value": {
                      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
                      "token_type": "Bearer",
                      "expires_at": "2026-09-09T18:14:10Z",
                      "scope": "sign",
                      "document_id": "8f14e45f-ea8f-4b2c-9f0e-1b2c3d4e5f60",
                      "template_id": null,
                      "recipient_id": "1f2e3d4c-5b6a-7980-9a0b-1c2d3e4f5a6b",
                      "session_id": "01J9K3P2S5N7Q8R1T2V3W4X5"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The link is valid but the caller may not pass yet. `meta.error` is\n`passcode_required`, `passcode_invalid`, or `not_your_turn` (which also carries\n`meta.waiting_on_signing_order`). The messages for `passcode_required` and\n`passcode_invalid` are deliberately identical; only the code differs.\n\nRepeated wrong passcodes for one recipient answer `429` with `passcode_locked`\nrather than `403`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignerError"
                },
                "examples": {
                  "passcodeRequired": {
                    "value": {
                      "message": "A passcode is required to open this document",
                      "meta": {
                        "error": "passcode_required",
                        "message": "A passcode is required to open this document",
                        "messages": [
                          "A passcode is required to open this document"
                        ]
                      },
                      "request_id": "3f9a1c74-8e2b-4d15-9a06-7c4e1b8d20f5"
                    }
                  },
                  "notYourTurn": {
                    "value": {
                      "message": "It is not your turn to sign yet",
                      "meta": {
                        "error": "not_your_turn",
                        "message": "It is not your turn to sign yet",
                        "messages": [
                          "It is not your turn to sign yet"
                        ],
                        "waiting_on_signing_order": 1
                      },
                      "request_id": "3f9a1c74-8e2b-4d15-9a06-7c4e1b8d20f5"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The document no longer accepts signer activity. `meta.error` is `document_expired`,\n`document_canceled`, `document_declined`, `document_not_sent`, or the generic\n`document_not_active`; `meta.state` carries the internal state.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignerError"
                },
                "examples": {
                  "canceled": {
                    "value": {
                      "message": "This document was canceled",
                      "meta": {
                        "error": "document_canceled",
                        "message": "This document was canceled",
                        "messages": [
                          "This document was canceled"
                        ],
                        "state": "canceled"
                      },
                      "request_id": "3f9a1c74-8e2b-4d15-9a06-7c4e1b8d20f5"
                    }
                  }
                }
              }
            }
          },
          "410": {
            "$ref": "#/components/responses/Gone"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "description": "Either the 50/min unauthenticated bucket is empty (`meta.error` is\n`rate_limit_exceeded`), or too many wrong passcodes have been presented for this\nrecipient and the gate is locked (`meta.error` is `passcode_locked`, with\n`meta.retry_after_seconds`). The lockout counter is per recipient, not per caller:\nit starts after five wrong passcodes and the wait doubles from 60 seconds with each\nfurther attempt, capped at an hour. A correct passcode presented during a lockout\nstill waits it out; one presented after it clears resets the counter.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignerError"
                },
                "examples": {
                  "passcodeLocked": {
                    "value": {
                      "message": "Too many incorrect passcodes. Try again later.",
                      "meta": {
                        "error": "passcode_locked",
                        "message": "Too many incorrect passcodes. Try again later.",
                        "messages": [
                          "Too many incorrect passcodes. Try again later."
                        ],
                        "retry_after_seconds": 120
                      },
                      "request_id": "3f9a1c74-8e2b-4d15-9a06-7c4e1b8d20f5"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/signer/session": {
      "get": {
        "tags": [
          "Signer"
        ],
        "operationId": "getSignerSession",
        "summary": "Get the signer's view of the document",
        "description": "Everything the current session is allowed to see: the document's settings, its files and\npage geometry in field units, this recipient, this recipient's own fields with their\nsaved values, the checkbox groups and conditional rules that apply, the gate state, and\nwhich actions are allowed.\n\nThis is also the refresh: every authenticated signer call slides the session's expiry,\nso a signer reading a long agreement does not lose their session mid-scroll while an\nabandoned tab still expires in 30 minutes. There is no separate refresh route.\n\nOther recipients' values appear in `completed_fields` only after the document is\ncomplete, and then as display values with no `api_id` a client could write back to.\n",
        "security": [
          {
            "SignerSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "The signer view.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignerSessionView"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SignerForbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signer/fields/{api_id}": {
      "parameters": [
        {
          "name": "api_id",
          "in": "path",
          "required": true,
          "description": "The field's `api_id`. It must belong to this session's recipient; anything else is a\n`404`, which is also what a field that does not exist answers.\n",
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "tags": [
          "Signer"
        ],
        "operationId": "saveSignerField",
        "summary": "Save a field value",
        "description": "Saves one field value. Consent must be recorded first, or this is `403`\n`consent_required`. The first saved value moves the document to `Pending`. Values on\nfields hidden by conditional rules are cleared at submit, never here.\n\nThe value is echoed back serialized exactly as the sender API serializes it: a checked\ncheckbox reads `\"t\"`, a date reads through its `date_format`.\n",
        "security": [
          {
            "SignerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignerFieldUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignerField"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SignerForbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/SignerConflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signer/consent": {
      "post": {
        "tags": [
          "Signer"
        ],
        "operationId": "recordSignerConsent",
        "summary": "Record ESIGN/UETA consent",
        "description": "Records the signer's agreement to sign electronically, bound to the\n`sent_revision_hash` they were shown. Required before any value can be saved. A hash\nthat does not match the document's frozen revision is `409 revision_mismatch`.\n\nThe response is the audit event itself, hash-chained to the one before it.\n",
        "security": [
          {
            "SignerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignerConsentRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Consent recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentEvent"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SignerForbidden"
          },
          "409": {
            "$ref": "#/components/responses/SignerConflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signer/signature": {
      "post": {
        "tags": [
          "Signer"
        ],
        "operationId": "createSignerSignature",
        "summary": "Store a signature or initials image",
        "description": "Uploads a drawn, typed, or uploaded signature and returns an asset id to apply to\nsignature and initials fields for the rest of the session.\n",
        "security": [
          {
            "SignerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignerSignatureRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The stored signature asset.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignatureAsset"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SignerForbidden"
          },
          "409": {
            "$ref": "#/components/responses/SignerConflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/signer/attachments/{attachment_request_id}": {
      "parameters": [
        {
          "name": "attachment_request_id",
          "in": "path",
          "required": true,
          "description": "The `attachment_requests[]` entry being satisfied.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Signer"
        ],
        "operationId": "uploadSignerAttachment",
        "summary": "Upload a requested attachment",
        "description": "Uploads the file the sender asked this recipient for. 25 MB maximum — an oversize file is\n`422`, because the cap is a validation rule and not a transport limit.\n\nAn `attachment_request_id` that is not one of this recipient's own is a `404`, as is a\nsession with no workspace behind it. A document that no longer accepts signer activity is\n`409`.\n",
        "security": [
          {
            "SignerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                },
                "required": [
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The stored attachment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignerAttachment"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SignerForbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/SignerConflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signer/submit": {
      "post": {
        "tags": [
          "Signer"
        ],
        "operationId": "submitSignerFields",
        "summary": "Submit this recipient's signature",
        "description": "Finishes this recipient's turn. The server re-evaluates conditional rules, clears the\nvalues of hidden fields and records the decisions, validates required fields, and records\nthe hash of the submitted values bound to `sent_revision_hash`. When this was the last\nsigner the document moves to `finalizing` under a row lock and the seal job runs; further\nsubmissions answer `409 document_finalizing`.\n",
        "security": [
          {
            "SignerSession": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignerSubmitRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The signer's turn is complete.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignerSubmitResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SignerForbidden"
          },
          "409": {
            "$ref": "#/components/responses/SignerConflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signer/decline": {
      "post": {
        "tags": [
          "Signer"
        ],
        "operationId": "declineSigning",
        "summary": "Decline to sign",
        "description": "Declines with a reason. The document becomes `Declined`, the reason is stored as\n`decline_message`, `document_declined` fires, and every link on the document is revoked —\nincluding this session. Allowed only while `allow_decline` is true, otherwise `403`\n`decline_not_allowed`.\n",
        "security": [
          {
            "SignerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignerDeclineRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Declined. This session is no longer valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignerDeclineResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SignerForbidden"
          },
          "409": {
            "$ref": "#/components/responses/SignerConflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signer/reassign": {
      "post": {
        "tags": [
          "Signer"
        ],
        "operationId": "reassignSigning",
        "summary": "Reassign to somebody else",
        "description": "Hands this turn to another person: a new recipient row is created, this recipient's\nfields move to it, `recipient_reassigned` is recorded, the replacement is notified, and\nthe old links — this session included — are revoked. Allowed only while `allow_reassign`\nis true and nothing has been signed, otherwise `403 reassign_not_allowed`.\n",
        "security": [
          {
            "SignerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignerReassignRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reassigned. This session is no longer valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignerReassignResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SignerForbidden"
          },
          "409": {
            "$ref": "#/components/responses/SignerConflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/signer/download": {
      "get": {
        "tags": [
          "Signer"
        ],
        "operationId": "getSignerDownload",
        "summary": "Download the signed copy",
        "description": "A short-lived link to the canonical `completed` artifact — stamped pages plus the audit\npage, sealed — once the document is complete and sealed.\n\nBefore then this is `403 download_not_available` with `meta.state`, not a `404`: no\nthird-party client keys on a status code here, and the signer app has a real page to\nshow for \"not finished yet\".\n",
        "security": [
          {
            "SignerSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "The download link and the artifact's hash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignerDownload"
                },
                "examples": {
                  "ready": {
                    "value": {
                      "url": "https://files.signclad.com/signed/8f14e45f.pdf?expires=1789050600&signature=...",
                      "kind": "completed",
                      "sha256": "7d3f9a1c5b8e2046d1f3a5c7e9b0d2f4a6c8e0b2d4f6a8c1e3b5d7f9a0c2e4b6",
                      "size": 284310,
                      "expires_in": 600
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/SignerForbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhook_endpoints/{id}/enable": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WebhookEndpointId"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "enableWebhookEndpoint",
        "summary": "Re-enable an endpoint",
        "description": "Brings a paused or disabled endpoint back to `active`. `replay: true` also re-queues\neverything it missed. The offer is returned either way, so a caller can show \"312 events\nare waiting\" before deciding.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "replay": {
                    "type": "boolean",
                    "default": false,
                    "description": "Re-queue everything the endpoint missed while it was off."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The endpoint, and what the replay did or would do.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "endpoint": {
                      "$ref": "#/components/schemas/WebhookEndpoint"
                    },
                    "replay": {
                      "$ref": "#/components/schemas/WebhookEnableReplay"
                    }
                  },
                  "required": [
                    "endpoint",
                    "replay"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhook_endpoints/{id}/deliveries/{delivery_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WebhookEndpointId"
        },
        {
          "$ref": "#/components/parameters/DeliveryId"
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "getWebhookDelivery",
        "summary": "Get one delivery",
        "description": "One delivery with everything the list form omits: the payload that was sent, the request\nheaders, the response excerpt, and the full attempt timeline. The list omits them because\na page of 25 document payloads is megabytes and the list is for scanning.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDelivery"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhook_endpoints/{id}/replay": {
      "parameters": [
        {
          "$ref": "#/components/parameters/WebhookEndpointId"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "replayWebhookDeliveryRange",
        "summary": "Replay a range of deliveries",
        "description": "\"Resend everything failed since.\" `since` is required: a replay with no floor would\nre-send a month of history, which is never what anybody meant.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "since": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "until": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "pending",
                      "delivered",
                      "failed",
                      "dead"
                    ]
                  },
                  "event_type": {
                    "$ref": "#/components/schemas/EventType"
                  }
                },
                "required": [
                  "since"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The replays were queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookReplaySummary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/verify/{hash}": {
      "parameters": [
        {
          "name": "hash",
          "in": "path",
          "required": true,
          "description": "Lowercase hex SHA-256 of the PDF being checked.",
          "schema": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$"
          }
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "verifyArtifactHash",
        "summary": "Verify a sealed file by hash",
        "description": "Public and unauthenticated. Takes the SHA-256 of a PDF somebody has in their hands and\nreports which sealed artifacts it matches, when each was sealed, whether its timestamp\nstill validates and how it was obtained, whether a daily anchor covers it, and the audit\nchain head at that moment. The file itself is never uploaded: `verify.signclad.com`\nhashes it in the browser and sends only the hash.\n\nAn unknown hash is a `200` with an empty `matches` array, not a `404`: \"this file is not\none of ours\" is an answer, and a `404` here would also let somebody probe for which\nhashes exist.\n\nMatches `completed`, `completed_no_audit` and `completed_lt`, and names which in\n`matches[].artifact`.\n",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "200": {
            "description": "What the hash matches. Empty when nothing does.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifyResponse"
                },
                "examples": {
                  "match": {
                    "summary": "A sealed completed PDF",
                    "value": {
                      "matches": [
                        {
                          "document_id": "8f14e45f-ea8f-4b2c-9f0e-1b2c3d4e5f60",
                          "artifact": "completed",
                          "sealed_at": "2026-09-09T17:44:15Z",
                          "timestamp_valid": true,
                          "chain_head": "7d3f9a1c5b8e2046d1f3a5c7e9b0d2f4a6c8e0b2d4f6a8c1e3b5d7f9a0c2e4b6",
                          "timestamp": {
                            "status": "detached",
                            "attached_at": "2026-09-09T19:02:41Z",
                            "source": "completed"
                          },
                          "anchor": {
                            "anchored_on": "2026-09-09",
                            "root_sha256": "4b2c9f0e1b2c3d4e5f60a1b2c3d4e5f607d3f9a1c5b8e2046d1f3a5c7e9b0d2f4",
                            "timestamped": true
                          }
                        }
                      ]
                    }
                  },
                  "unknown": {
                    "summary": "Not one of ours",
                    "value": {
                      "matches": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/email_suppressions": {
      "get": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "listEmailSuppressions",
        "summary": "List suppressed addresses",
        "description": "Addresses this workspace can no longer mail, and why. A suppression list nobody can read\nis a black hole: a sender whose recipient bounced once should be able to see why the\nlater invitations are not arriving.\n\nOwner and admin only — the list is a list of the workspace's recipients' email\naddresses. An API key is treated as an admin credential, matching `GET /me`. Released\nrows stay readable but are out of the default listing, which is about what is blocked\nnow.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "account:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "email",
            "in": "query",
            "required": false,
            "description": "Exact address, normalized before matching.",
            "schema": {
              "type": "string",
              "format": "email"
            }
          },
          {
            "name": "reason",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "hard_bounce",
                "complaint",
                "manual"
              ]
            }
          },
          {
            "name": "include_released",
            "in": "query",
            "required": false,
            "description": "Include suppressions that have already been lifted.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/CursorLimit"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of suppressions, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailSuppressionList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/email_suppressions/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The suppression's id.",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "delete": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "releaseEmailSuppression",
        "summary": "Lift a suppression",
        "description": "Lets Signclad mail the address again. The row is kept and marked released rather than\ndeleted: releasing an address that hard bounced twice is a different decision from\nreleasing one that bounced once, and a delete would hide the difference.\n\nA suppression in another workspace, or one already released, is a `404` — never a `403`,\nwhich would confirm the id exists somewhere.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "204": {
            "description": "Released."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhook_inbox": {
      "get": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "listWebhookInboxes",
        "summary": "List debugging inboxes",
        "description": "Hosted URLs that record whatever is posted to them, so a developer can see real payloads\nbefore writing a receiver.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The workspace's inboxes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookInbox"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "createWebhookInbox",
        "summary": "Create a debugging inbox",
        "description": "Mints a new inbox and its public URL.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The inbox.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookInbox"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhook_inbox/{id}/requests": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The inbox's id.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "listWebhookInboxRequests",
        "summary": "Read what an inbox recorded",
        "description": "Everything posted to the inbox, newest first, with headers, query, and body.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "webhooks:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CursorLimit"
          }
        ],
        "responses": {
          "200": {
            "description": "The recorded requests.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookInboxRequest"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/inbox/{token}": {
      "parameters": [
        {
          "name": "token",
          "in": "path",
          "required": true,
          "description": "The inbox's token. It is the whole credential, which is why this route needs no other.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "ingestWebhookInbox",
        "summary": "Post anything to a debugging inbox",
        "description": "Records the request and answers `200` to anything, whatever the body or content type. A\ndebugging inbox that returned errors would teach the sender the wrong lesson.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            },
            "text/plain": {
              "schema": {
                "type": "string"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "received": {
                      "type": "boolean"
                    },
                    "request_id": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "received"
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/inbound/postmark": {
      "post": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "ingestPostmarkEvent",
        "summary": "Postmark delivery report",
        "description": "Internal. Bounce, complaint, and delivery reports from Postmark, authenticated by a\nshared secret in the request rather than by a Signclad credential; the controller refuses\nby default when that secret is unconfigured, because an unauthenticated bounce endpoint\nlets a stranger mark any recipient's address dead.\n\nAnswers `2xx` even for a payload it cannot match — an unmatched message id is not an\nerror, it is a message this environment never sent, and a `4xx` would make the provider\nretry and eventually disable the endpoint.\n\nA secret that does not check out is `403 invalid_signature` in the standard forbidden\nenvelope, matching the SES and Twilio routes. It answered a bare `401` with no code until\nthe error-envelope pass.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Processed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InboundResult"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/inbound/ses": {
      "post": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "ingestSesEvent",
        "summary": "Amazon SES delivery report",
        "description": "Internal. SNS notifications for SES bounces, complaints, and deliveries, authenticated by\nthe SNS message signature plus a topic allowlist. Subscription confirmations are handled\nhere too. Same `2xx`-for-anything-unmatched rule as the Postmark route.\n\nA signature that does not verify is `403 invalid_signature`; a topic that is not in the\nallowlist is `403 unknown_topic`, which is a different fact and gets a different code.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            },
            "text/plain": {
              "schema": {
                "type": "string",
                "description": "SNS posts its JSON with a `Content-Type` of `text/plain`."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Processed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InboundResult"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/document_templates/{id}/revisions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/TemplateId"
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Templates"
        ],
        "operationId": "listTemplateRevisions",
        "summary": "List template revisions",
        "description": "Every saved revision, newest first, with the reason it was created and the hash of its\nsnapshot. `current_revision` is the number to pass as `expected_revision` when replacing\na file.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "templates:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The revisions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TemplateRevision"
                      }
                    },
                    "current_revision": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/document_templates/{id}/revisions/{number}/restore": {
      "parameters": [
        {
          "$ref": "#/components/parameters/TemplateId"
        },
        {
          "name": "number",
          "in": "path",
          "required": true,
          "description": "The revision number to restore.",
          "schema": {
            "type": "integer",
            "minimum": 0
          }
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Templates"
        ],
        "operationId": "restoreTemplateRevision",
        "summary": "Restore a template revision",
        "description": "Recreates an old snapshot as a **new** revision rather than rewinding history, so field\nlayout and file always match and nothing that pinned an earlier revision changes under\nit.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The template at its new revision.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/register": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root (these routes are not under /api/v1)"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "register",
        "summary": "Create an account",
        "description": "Creates a user, their account, and their first workspace, and signs them in. Session\ncookie, not a token: call `GET /sanctum/csrf-cookie` first and send the `X-XSRF-TOKEN`\nheader, as with every route on the API root. A six-digit, ten-minute verification code is\nemailed immediately; until it is accepted, the session can only call `GET /api/v1/me` and\nthe verification routes.\n\n6 requests per minute per IP. `403` when registration is closed, `409` when the caller is\nalready signed in.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered and signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Already signed in. This status is not in the shared renderer, so the body is\nLaravel's default `{\"message\": \"You are already signed in.\"}` rather than the usual\nenvelope.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "`errors.email` reads `An account with this email already exists. Sign in instead.`\nfor a taken address — including when two registrations race — or\n`This workspace requires single sign-on.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/login": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "login",
        "summary": "Sign in",
        "description": "Signs in with an email and password, 6 attempts per minute per IP.\n\n**Three different `200` bodies**, none of which share a discriminating property with\nanother; branch on the flag each carries.\n\n- No second factor needed: the session payload.\n- A second factor needed: a challenge, `mfa_required: true` plus the methods to offer.\n  Still a `200`, not a `4xx`, because nothing went wrong.\n- The address belongs to a workspace that enforces SSO: `sso_required: true` with the `url`\n  to send the person to and a message to show. It says nothing about whether the account\n  exists — the answer depends on the domain, which the caller already knows, and is the\n  same for an address that never signed up. A workspace owner is exempt, as break-glass,\n  and falls through to the password path.\n\nA `422` carrying `sign_in_with: \"google\"` is the one deliberate exception to \"one message\nfor every failure\": the account has a Google identity and no password, so no guess can\nsucceed and the honest answer is the one that stops the guessing. The attempt still counts\ntoward lockout.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in, or challenged for a second factor.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/SessionPayload"
                    },
                    {
                      "$ref": "#/components/schemas/MfaChallenge"
                    },
                    {
                      "$ref": "#/components/schemas/SsoRequired"
                    }
                  ]
                },
                "examples": {
                  "signedIn": {
                    "$ref": "#/components/examples/SessionPayloadExample"
                  },
                  "challenged": {
                    "$ref": "#/components/examples/MfaChallengeExample"
                  }
                }
              }
            }
          },
          "422": {
            "description": "`errors.email` reads `These credentials do not match our records.` — the same message\nwhether the address is unknown or the password is wrong. A Google-only account answers\nhere too, with `sign_in_with: \"google\"` alongside the usual `errors.email`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ValidationErrorBody"
                    },
                    {
                      "type": "object",
                      "additionalProperties": true,
                      "properties": {
                        "sign_in_with": {
                          "type": "string",
                          "enum": [
                            "google"
                          ],
                          "description": "Present only when the address has a Google identity and no password."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/login/mfa": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "completeMfaChallenge",
        "summary": "Answer a second-factor challenge",
        "description": "Completes the challenge `POST /login` (or a magic link) started. The pending challenge\nlives in the session, so nothing identifies it in the body. 10 attempts per minute per IP.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MfaChallengeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionPayloadWithRecovery"
                }
              }
            }
          },
          "422": {
            "description": "`errors.method` reads `The sign-in attempt expired. Start again.` when no challenge is\npending. `errors.code` reads `That code is not valid.`,\n`The sign-in attempt expired. Start again.`, or\n`That passkey could not be verified.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/logout": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "logout",
        "summary": "Sign out",
        "description": "Clears this session. Always `200`, even when nobody was signed in.",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "200": {
            "description": "Signed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkResponse"
                }
              }
            }
          }
        }
      }
    },
    "/logout/everywhere": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "logoutEverywhere",
        "summary": "Sign out of every session",
        "description": "Revokes every session for this user, this one included. The count of revoked sessions goes\nto the security event log, not to the response.\n",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "200": {
            "description": "Signed out everywhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkResponse"
                }
              }
            }
          }
        }
      }
    },
    "/auth/magic_link": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "requestMagicLink",
        "summary": "Request a sign-in link",
        "description": "Emails a single-use sign-in link. The response is identical whether or not the address has\nan account — that is the point — so it never confirms who is registered. 5 per minute\nper IP.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 255
                  }
                },
                "required": [
                  "email"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent, if there was somewhere to send it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "sent": {
                      "type": "boolean"
                    },
                    "expires_in_minutes": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "sent"
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/auth/magic_link/consume": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "consumeMagicLink",
        "summary": "Sign in with a link",
        "description": "Exchanges the token from a sign-in link for a session. Like `POST /login` this answers\neither the session payload or a second-factor challenge, both as `200`. The session\npayload here carries no `mfa` block and reports `email_verified: true` — following the\nlink is itself proof of the address.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "token": {
                    "type": "string",
                    "maxLength": 255
                  }
                },
                "required": [
                  "token"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in, or challenged for a second factor.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/MagicLinkSession"
                    },
                    {
                      "$ref": "#/components/schemas/MfaChallenge"
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "`errors.token` reads `That sign-in link is no longer valid. Ask for a new one.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/auth/password/forgot": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "requestPasswordReset",
        "summary": "Request a password reset",
        "description": "The same silence as the magic link — `{\"sent\": true}` whoever asked. 5 per minute per IP.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 255
                  }
                },
                "required": [
                  "email"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent, if there was somewhere to send it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "sent": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "sent"
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/auth/password/reset": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "resetPassword",
        "summary": "Set a new password from a reset link",
        "description": "Sets the password and revokes every existing session, returning how many. It does **not**\nsign the caller in; they log in with the new password.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "token": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "password": {
                    "$ref": "#/components/schemas/NewPassword"
                  }
                },
                "required": [
                  "token",
                  "password"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Password changed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "sessions_revoked": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          },
          "422": {
            "description": "`errors.token` reads `That reset link is no longer valid. Ask for a new one.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/auth/email/verify": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "verifyEmailCode",
        "summary": "Verify a new account's email address",
        "description": "Accepts the six-digit code sent after password registration. The code expires after ten\nminutes, works once, is bound to the signed-in browser session and current email address,\nand is retired after five failed guesses.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "code": {
                    "type": "string",
                    "pattern": "^\\d{6}$"
                  }
                },
                "required": [
                  "code"
                ]
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Email verified, or it was already verified."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "description": "The code is malformed, invalid, expired, or exhausted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/auth/email/verify/resend": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "resendEmailVerification",
        "summary": "Send a fresh verification code",
        "description": "Replaces the current code and sends a fresh one to the signed-in user's address. Always\n`204` when the address is already verified or nobody is signed in. Limited to one send per\nminute and five per hour for the account, plus 3 requests per minute per IP.\n",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "204": {
            "description": "Nothing to report, by design."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/auth/email/verify/{id}/{hash}": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The user's id.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "hash",
          "in": "path",
          "required": true,
          "description": "SHA-1 of the address being verified.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Authentication"
        ],
        "operationId": "verifyEmail",
        "summary": "Verify an email address",
        "description": "The link in the verification email. It is a Laravel signed URL — a tampered one is `403`\nbefore the controller runs — and it always **redirects** to the web app rather than\nanswering JSON, with `?status=verified`, `already_verified`, or `invalid`.\n",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "302": {
            "description": "Redirect to the web app's `/auth/email-verified` page with a `status` query parameter.",
            "headers": {
              "Location": {
                "description": "Where the browser is sent.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/auth/google/redirect": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "get": {
        "tags": [
          "Authentication"
        ],
        "operationId": "startGoogleSignIn",
        "summary": "Start Google sign-in",
        "description": "Redirects to Google with the `openid profile email` scopes. 20 per minute per IP.",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "302": {
            "description": "Redirect to Google.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/auth/google/callback": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "get": {
        "tags": [
          "Authentication"
        ],
        "operationId": "completeGoogleSignIn",
        "summary": "Finish Google sign-in",
        "description": "Google's redirect target. Always redirects to the web app, never JSON: on success to the\nconfigured landing path, on failure to the login page with `?error=google_failed`,\n`google_no_email`, or `google_link_requires_verified_email`.\n",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "302": {
            "description": "Redirect to the web app.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          }
        }
      }
    },
    "/contact": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "submitContactForm",
        "summary": "Send a message to Signclad",
        "description": "The marketing site's contact form. Session-backed and CSRF-protected like the rest of the root.",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 100,
                    "description": "No line breaks — a header-injection guard."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "subject": {
                    "type": "string",
                    "maxLength": 150,
                    "description": "No line breaks."
                  },
                  "message": {
                    "type": "string",
                    "maxLength": 10000
                  }
                },
                "required": [
                  "name",
                  "email",
                  "subject",
                  "message"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/security/events": {
      "get": {
        "tags": [
          "Security"
        ],
        "operationId": "listSecurityEvents",
        "summary": "List security events",
        "description": "The person's own security log: sign-ins, failures, lockouts, MFA changes, passkeys,\nsessions, password and email changes. Cursor-paginated, newest first.\n\nEvery route under `/security` requires a **person**. An API key authenticates and is then\nrefused `403`: a key has no TOTP secret, no sessions, and no security log of its own. These\nroutes are also the one place exempt from the MFA-enrolment requirement — the only way to\nenrol must not itself require enrolment.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "One event type from the catalog below.",
            "schema": {
              "$ref": "#/components/schemas/SecurityEventType"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of events.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SecurityEvent"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/security/mfa": {
      "get": {
        "tags": [
          "Security"
        ],
        "operationId": "getMfaStatus",
        "summary": "Get MFA status",
        "description": "What second factors this person has, whether their role requires one, and how long they\nhave to enrol. Also lists their passkeys and whether a TOTP enrolment is waiting to be\nconfirmed.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MfaStatusWithPasskeys"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/security/mfa/totp/setup": {
      "post": {
        "tags": [
          "Security"
        ],
        "operationId": "setupTotp",
        "summary": "Start authenticator enrolment",
        "description": "Returns the shared secret in three forms — raw base32, an `otpauth://` URI, and a rendered\nQR code as inline SVG markup — plus the parameters an authenticator needs. Nothing is\nenrolled until `confirm` succeeds.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The enrolment material.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TotpSetup"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.totp` reads `An authenticator app is already set up. Remove it before adding another.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/security/mfa/totp/confirm": {
      "post": {
        "tags": [
          "Security"
        ],
        "operationId": "confirmTotp",
        "summary": "Confirm authenticator enrolment",
        "description": "Proves the authenticator works and completes enrolment. `recovery_codes` is returned only\nwhen this enrolment generated the person's first set — otherwise it is `null` and the\nexisting codes still stand.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "code": {
                    "type": "string",
                    "maxLength": 16
                  }
                },
                "required": [
                  "code"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Enrolled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "enrolled": {
                      "type": "boolean"
                    },
                    "recovery_codes": {
                      "type": [
                        "array",
                        "null"
                      ],
                      "items": {
                        "type": "string"
                      }
                    },
                    "mfa": {
                      "$ref": "#/components/schemas/MfaStatus"
                    }
                  },
                  "required": [
                    "enrolled"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.code` reads `That code is not valid.`; `errors.totp` reads\n`Start the setup again: there is no enrolment waiting to be confirmed.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/security/mfa/totp": {
      "delete": {
        "tags": [
          "Security"
        ],
        "operationId": "removeTotp",
        "summary": "Remove the authenticator",
        "description": "Takes a `password` in the body — unusual on a `DELETE`, and required whenever the account\nhas a password at all. Refused when the person's role requires a second factor and this is\ntheir last one.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "password": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Required in practice whenever the account has a password."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "removed": {
                      "type": "boolean"
                    },
                    "mfa": {
                      "$ref": "#/components/schemas/MfaStatus"
                    }
                  },
                  "required": [
                    "removed"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.password` reads `The current password is incorrect.`; `errors.totp` reads\n`Your role requires two-factor authentication. Add another factor before removing this one.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/security/mfa/passkeys": {
      "get": {
        "tags": [
          "Security"
        ],
        "operationId": "listPasskeys",
        "summary": "List passkeys",
        "description": "This person's registered passkeys, newest first.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The passkeys.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Passkey"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/security/mfa/passkeys/register/options": {
      "post": {
        "tags": [
          "Security"
        ],
        "operationId": "getPasskeyRegistrationOptions",
        "summary": "Get passkey creation options",
        "description": "Returns the WebAuthn `PublicKeyCredentialCreationOptions` to hand to\n`navigator.credentials.create()`, **unwrapped at the top level**. The object is passed\nthrough from the WebAuthn library with null values omitted, so optional members are absent\nrather than null and the exact key set follows that library rather than this document.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The creation options, verbatim.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebAuthnOptions"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.passkeys` reads `You have reached the maximum number of passkeys.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/security/mfa/passkeys/register/verify": {
      "post": {
        "tags": [
          "Security"
        ],
        "operationId": "verifyPasskeyRegistration",
        "summary": "Finish registering a passkey",
        "description": "Verifies the attestation from `navigator.credentials.create()` and stores the passkey.\n`recovery_codes` comes back only when this was the person's first second factor.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasskeyRegistrationRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "passkey": {
                      "$ref": "#/components/schemas/Passkey"
                    },
                    "recovery_codes": {
                      "type": [
                        "array",
                        "null"
                      ],
                      "items": {
                        "type": "string"
                      }
                    },
                    "mfa": {
                      "$ref": "#/components/schemas/MfaStatus"
                    }
                  },
                  "required": [
                    "passkey"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.credential` reads `The sign-in attempt expired. Start again.` or\n`That passkey could not be verified.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/security/mfa/passkeys/{passkey}": {
      "parameters": [
        {
          "name": "passkey",
          "in": "path",
          "required": true,
          "description": "The passkey's id.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "patch": {
        "tags": [
          "Security"
        ],
        "operationId": "renamePasskey",
        "summary": "Rename a passkey",
        "description": "Only the name can change. Ownership is checked before validation, so a passkey belonging to\nsomebody else is `404` even when the body is also invalid.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 60
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Renamed. No `mfa` block here.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "passkey": {
                      "$ref": "#/components/schemas/Passkey"
                    }
                  },
                  "required": [
                    "passkey"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      },
      "delete": {
        "tags": [
          "Security"
        ],
        "operationId": "removePasskey",
        "summary": "Remove a passkey",
        "description": "No password re-authentication here, unlike removing an authenticator. Refused when the\nperson's role requires a second factor and this is their last one.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "removed": {
                      "type": "boolean"
                    },
                    "mfa": {
                      "$ref": "#/components/schemas/MfaStatus"
                    }
                  },
                  "required": [
                    "removed"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "`errors.passkey` reads `Your role requires two-factor authentication. Add another factor before removing this one.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/security/mfa/recovery_codes": {
      "get": {
        "tags": [
          "Security"
        ],
        "operationId": "getRecoveryCodeStatus",
        "summary": "Count remaining recovery codes",
        "description": "How many codes are left, and how many a fresh set contains. The codes themselves are never returned here.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The counts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "remaining": {
                      "type": "integer"
                    },
                    "total": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "remaining",
                    "total"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Security"
        ],
        "operationId": "regenerateRecoveryCodes",
        "summary": "Generate a new set of recovery codes",
        "description": "Replaces any existing codes and returns the new ones — the only time they are shown. Takes\n`password` when the account has one.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "password": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new codes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "recovery_codes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "remaining": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "recovery_codes",
                    "remaining"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.password` reads `The current password is incorrect.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/security/password": {
      "put": {
        "tags": [
          "Security"
        ],
        "operationId": "changePassword",
        "summary": "Change the password",
        "description": "Revokes every other session and reports how many; the current session survives.\n`current_password` is required whenever the account has one — a Google-only account has\nnone. The new password is checked against a breach corpus, so a long password can still be\nrefused.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "current_password": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "password": {
                    "$ref": "#/components/schemas/NewPassword"
                  }
                },
                "required": [
                  "password"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Changed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "sessions_revoked": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.current_password` reads `The current password is incorrect.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/security/sessions": {
      "get": {
        "tags": [
          "Security"
        ],
        "operationId": "listSessions",
        "summary": "List active sessions",
        "description": "Every signed-in session for this person, with the current one flagged, and the session policy.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The sessions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UserSession"
                      }
                    },
                    "policy": {
                      "type": "object",
                      "additionalProperties": true,
                      "properties": {
                        "idle_timeout_minutes": {
                          "type": "integer"
                        },
                        "absolute_lifetime_hours": {
                          "type": "integer"
                        }
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/security/sessions/revoke_others": {
      "post": {
        "tags": [
          "Security"
        ],
        "operationId": "revokeOtherSessions",
        "summary": "Sign out every other session",
        "description": "Keeps this session and revokes the rest. `revoked` is a count, as it is on\n`DELETE /security/sessions/{session}`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "How many were revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "revoked": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "revoked"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/security/sessions/{session}": {
      "parameters": [
        {
          "name": "session",
          "in": "path",
          "required": true,
          "description": "The session's id.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Security"
        ],
        "operationId": "revokeSession",
        "summary": "Revoke one session",
        "description": "Revoking the current session is allowed, and `was_current` says whether that is what\nhappened. `revoked` counts what was revoked, the same as on `revoke_others`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "revoked": {
                      "type": "integer"
                    },
                    "was_current": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "revoked"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/workspaces": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "operationId": "listWorkspaces",
        "summary": "List workspaces",
        "description": "Every workspace the caller can reach, sorted by name. Readable by any role.\n\nAn API key belongs to exactly one workspace, so it sees a single entry with `role: admin`.\nThe list item is a **smaller object** than every other workspace response here — no\n`default_language`, or `created_at` — and its `test_mode` is\nalways `false` for a person, whatever the key would report.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "account:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The workspaces.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WorkspaceListItem"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Accounts"
        ],
        "operationId": "createWorkspace",
        "summary": "Create a workspace",
        "description": "Adds a workspace to the caller's account. Requires `manage_settings`. Documents, templates,\nAPI keys, and webhooks all live inside a workspace; billing does not.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "default_language": {
                    "type": "string",
                    "maxLength": 8
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/workspaces/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The workspace's id. One outside the caller's account is a `404`, never a `403`.",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Accounts"
        ],
        "operationId": "getWorkspace",
        "summary": "Get a workspace",
        "description": "One workspace, as a bare object. Readable by any member; somebody with no role in it gets a\n`404`, not a `403`, so the id is never confirmed.\n\nThis is the only workspace shape that carries `created_at` — the list omits it.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Accounts"
        ],
        "operationId": "updateWorkspace",
        "summary": "Update a workspace",
        "description": "Renames a workspace and changes its default language, compatibility shim, and\nidle timeout. Requires `manage_settings` both on the current workspace and on the one being\nchanged.\n\nThe storage region is **not** changed here — it is a migration, not a setting, and it has\nits own route.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "default_language": {
                    "type": "string",
                    "maxLength": 8
                  },
                  "idle_timeout_minutes": {
                    "type": "integer",
                    "description": "How long a session may sit idle before it is signed out. One of a fixed set,\nwhich deployment configuration decides; the values below are the defaults.\n",
                    "enum": [
                      15,
                      30,
                      60,
                      240,
                      720
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      },
      "delete": {
        "tags": [
          "Accounts"
        ],
        "operationId": "deleteWorkspace",
        "summary": "Schedule a workspace for deletion",
        "description": "Owner only — an API key never has `manage_billing`. Answers `202`: the workspace is marked\ndeleted now and purged at `purge_at`. An account must keep at least one workspace; deleting\nthe last one is `422`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "202": {
            "description": "Scheduled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean"
                    },
                    "purge_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "deleted"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "`errors.workspace` reads `An account must keep at least one workspace. Cancel the subscription instead.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/workspaces/{id}/switch": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "tags": [
          "Accounts"
        ],
        "operationId": "switchWorkspace",
        "summary": "Switch the active workspace",
        "description": "Points this session at another of the caller's workspaces. People only: an API key is\n`403`, because a key is bound to one workspace by construction. A workspace the caller has\nno role in is `404`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The workspace now in context.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "workspace": {
                      "$ref": "#/components/schemas/Workspace"
                    }
                  },
                  "required": [
                    "workspace"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/workspaces/{id}/transfer_ownership": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "tags": [
          "Accounts"
        ],
        "operationId": "transferWorkspaceOwnership",
        "summary": "Transfer ownership",
        "description": "Hands the owner role to another member, named either by `membership_id` or by `user_id`.\nOwner only. This is the only way a workspace's owner changes: `PATCH /members/{id}` refuses\nto grant `owner`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "membership_id": {
                    "type": "string"
                  },
                  "user_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The workspace and its new owner.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "workspace": {
                      "$ref": "#/components/schemas/Workspace"
                    },
                    "owner": {
                      "type": "object",
                      "additionalProperties": true,
                      "properties": {
                        "membership_id": {
                          "type": "string"
                        },
                        "user_id": {
                          "type": "string"
                        },
                        "email": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "email"
                        }
                      }
                    }
                  },
                  "required": [
                    "workspace"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "`errors.member` reads `That person is not in this workspace.` or\n`That membership is archived.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/members": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "operationId": "listMembers",
        "summary": "List members",
        "description": "Everyone in the workspace, oldest first, archived members included. Readable by any role —\nunlike the invitation list, which needs `manage_members`.\n\nThe list item carries `seat` and `created_at`; the item returned by the role, archive, and\nrestore operations does not.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "account:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The members.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MemberListItem"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/members/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The membership's id.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "patch": {
        "tags": [
          "Accounts"
        ],
        "operationId": "updateMemberRole",
        "summary": "Change a member's role",
        "description": "People only — an API key is `403`. Nobody can change their own role, an admin cannot touch\nan owner, and only an owner may grant `owner`; in practice granting `owner` goes through\n`POST /workspaces/{id}/transfer_ownership`, which is what keeps exactly one owner.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "owner",
                      "admin",
                      "member",
                      "viewer"
                    ]
                  }
                },
                "required": [
                  "role"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated membership.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Member"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "`errors.role` reads `That is not a role.`, `That workspace no longer exists.`, or\n`A workspace must keep an owner. Transfer ownership first.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/members/{id}/archive": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Accounts"
        ],
        "operationId": "archiveMember",
        "summary": "Archive a member",
        "description": "Takes away access and frees the seat while keeping the row, so their documents keep their\nauthor. Their templates transfer to the owner. Idempotent: archiving an archived member is\n`200` and changes nothing.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The archived membership.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Member"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "`errors.member` reads `That workspace no longer exists.` or\n`A workspace must keep an owner. Transfer ownership first.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/members/{id}/restore": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Accounts"
        ],
        "operationId": "restoreMember",
        "summary": "Restore an archived member",
        "description": "Gives access back and takes a seat again.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The restored membership.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Member"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/members/invitations": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "operationId": "listInvitations",
        "summary": "List pending invitations",
        "description": "Invitations that have neither been accepted nor revoked, newest first. Requires\n`manage_members`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The pending invitations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invitation"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Accounts"
        ],
        "operationId": "createInvitation",
        "summary": "Invite someone",
        "description": "Emails an invitation and returns the token and accept URL — the only response that carries\nthem. People only. `owner` cannot be invited; transfer ownership instead. Inviting an\naddress that already has a pending invitation resends it rather than failing.\n\n`adds_seat` says whether accepting will bill another seat.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "admin",
                      "member",
                      "viewer"
                    ]
                  }
                },
                "required": [
                  "email",
                  "role"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The invitation, with its token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvitationWithToken"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.email` reads `That person is already in this workspace.`; `errors.role` reads\n`That is not a role.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/members/invitations/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Accounts"
        ],
        "operationId": "revokeInvitation",
        "summary": "Revoke an invitation",
        "description": "Only a pending invitation can be revoked; anything else is `422`.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "204": {
            "description": "Revoked."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "`errors.invitation` reads `That invitation is not pending.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/invitations/{token}/accept": {
      "parameters": [
        {
          "name": "token",
          "in": "path",
          "required": true,
          "description": "The token from the invitation email.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Accounts"
        ],
        "operationId": "acceptInvitation",
        "summary": "Accept an invitation",
        "description": "Joins the workspace. The caller must be signed in but is, by definition, not yet a member,\nso this route carries neither the workspace requirement nor a role check — the token is the\nauthorization. The invitation must have been sent to the signed-in address.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The new membership.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "membership": {
                      "type": "object",
                      "additionalProperties": true,
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "role": {
                          "type": "string"
                        }
                      }
                    },
                    "workspace": {
                      "oneOf": [
                        {
                          "type": "object",
                          "additionalProperties": true,
                          "properties": {
                            "id": {
                              "type": "string"
                            },
                            "name": {
                              "type": "string"
                            },
                            "slug": {
                              "type": "string"
                            }
                          }
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "membership"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "description": "`errors.token` reads `This invitation is no longer valid.`,\n`This invitation was sent to a different email address.`, or\n`That workspace no longer exists.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api_keys": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "operationId": "listApiKeys",
        "summary": "List API keys",
        "description": "Every key in the workspace, newest first, revoked and expired ones included. Requires\n`manage_settings`. The secret is never returned here — only `display_prefix` and\n`last_four`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The keys.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKey"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Accounts"
        ],
        "operationId": "createApiKey",
        "summary": "Create an API key",
        "description": "Returns the key object with its plaintext `key` — the only time it exists. A sandbox key is\nrequested either as `sandbox: true` or `mode: sandbox` (`test` is accepted as a synonym);\n`sandbox` wins when both are sent, and the response always reports `mode` as `sandbox` or\n`live`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "live",
                      "sandbox",
                      "test"
                    ]
                  },
                  "sandbox": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The key, with its secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyWithSecret"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.sandbox` reads `Sandbox keys are not available on this plan.`; `errors.base`\nreads `The API is not available on this plan.` or\n`This workspace already has the maximum number of API keys. Revoke one first.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api_keys/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Accounts"
        ],
        "operationId": "revokeApiKey",
        "summary": "Revoke an API key",
        "description": "Immediate and idempotent. A key cannot revoke itself — that is a `409`, so an automated\nrotation cannot lock itself out halfway through.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "204": {
            "description": "Revoked."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api_keys/{id}/rotate": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Accounts"
        ],
        "operationId": "rotateApiKey",
        "summary": "Rotate an API key",
        "description": "Mints a replacement and leaves the old key working until `previous.stops_working_at`, so a\ndeploy can roll. Answers `200`, not `201`. The new plaintext is at `key.key`.\n\nThe overlap is a fixed 24 hours, set by deployment configuration. There is no request\nbody and no way to ask for a different window.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The new key and the outgoing one.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "key": {
                      "$ref": "#/components/schemas/ApiKeyWithSecret"
                    },
                    "previous": {
                      "$ref": "#/components/schemas/RotatedApiKey"
                    }
                  },
                  "required": [
                    "key"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "`errors.base` reads `That key no longer belongs to a workspace.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/billing": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "operationId": "getBilling",
        "summary": "Get the billing summary",
        "description": "Plan, seats, usage for a period, card, invoices, and the plan's feature matrix. Owner only:\nan API key resolves to `admin` and is refused, so a leaked integration key cannot see or\nchange billing.\n\n`period` is a `YYYY-MM` string; anything that does not look like one is ignored rather than\nrejected, and the current month is used.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "period",
            "in": "query",
            "required": false,
            "description": "Usage period. Silently ignored unless it matches `YYYY-MM`.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingSummary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "503": {
            "$ref": "#/components/responses/BillingUnavailable"
          }
        }
      }
    },
    "/billing/plan": {
      "patch": {
        "tags": [
          "Accounts"
        ],
        "operationId": "updateBillingPlan",
        "summary": "Change the plan",
        "description": "Switches plan or billing interval and returns the whole billing summary, with usage for the\ncurrent month.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlanChangeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingSummary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "description": "`errors.plan` reads `That is not a plan.`, `This account has no billing customer yet.`,\nor `That plan has no price configured. Contact support.`; `errors.interval` reads\n`That is not a billing interval.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/BillingUnavailable"
          }
        }
      }
    },
    "/billing/checkout_session": {
      "post": {
        "tags": [
          "Accounts"
        ],
        "operationId": "createCheckoutSession",
        "summary": "Start a checkout session",
        "description": "Returns a hosted checkout URL for a paid plan. `free` passes validation but is refused by\nthe business rule — there is nothing to buy.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlanChangeRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StripeSession"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "description": "`errors.plan` reads `That plan cannot be bought.`,\n`Billing is not configured in this environment.`, or\n`That plan has no price configured.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/BillingUnavailable"
          }
        }
      }
    },
    "/billing/portal_session": {
      "post": {
        "tags": [
          "Accounts"
        ],
        "operationId": "createPortalSession",
        "summary": "Open the billing portal",
        "description": "A hosted URL where the owner manages the card, invoices, and cancellation.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "201": {
            "description": "The session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StripeSession"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "description": "`errors.base` reads `Billing is not configured in this environment.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/BillingUnavailable"
          }
        }
      }
    },
    "/billing/cancel": {
      "post": {
        "tags": [
          "Accounts"
        ],
        "operationId": "cancelSubscription",
        "summary": "Cancel the subscription",
        "description": "Ends the subscription at the period end, or straight away with `immediately: true`. Returns\nthe whole billing summary.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "immediately": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingSummary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/BillingUnavailable"
          }
        }
      }
    },
    "/audit_log": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "operationId": "listAuditLog",
        "summary": "List the workspace audit log",
        "description": "Who did what in this workspace, newest first, plus account-level entries such as plan\nchanges. Requires `manage_members`. Cursor-paginated; the filters are applied as given and\nnever rejected.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "action",
            "in": "query",
            "required": false,
            "description": "Exact match on the action name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of entries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AuditLogEntry"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "get": {
        "tags": [
          "OAuth"
        ],
        "operationId": "getAuthorizationServerMetadata",
        "summary": "Authorization server metadata",
        "description": "RFC 8414 discovery. Everything a client needs to talk to the authorization server without\nhard-coding a URL. Cached for five minutes.\n\nThe same document is served from any path suffix (`/.well-known/oauth-authorization-server/anything`),\nbecause clients differ on where they look.\n",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "200": {
            "description": "The metadata document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthorizationServerMetadata"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "get": {
        "tags": [
          "OAuth"
        ],
        "operationId": "getProtectedResourceMetadata",
        "summary": "Protected resource metadata",
        "description": "RFC 9728 discovery: which authorization server protects this API, which scopes it\nunderstands, and how long a resource server may cache an introspection result.\n",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "200": {
            "description": "The metadata document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedResourceMetadata"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/authorize": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "get": {
        "tags": [
          "OAuth"
        ],
        "operationId": "showAuthorizationRequest",
        "summary": "Show the consent screen",
        "description": "The authorization endpoint. PKCE is mandatory: `code_challenge` with\n`code_challenge_method=S256`, no exceptions and no default. `response_type` must be `code`;\nthere is no implicit grant.\n\n**The screen is skipped when there is nothing to decide.** A grant that already covers this\nclient, this audience, and every requested scope is honoured silently and the browser goes\nstraight back with a code — showing a consent screen for something already approved only\nteaches people to click through consent screens. Three things force it anyway:\n`prompt=consent`, a request for a scope the existing grant does not carry, and a grant whose\nworkspace the person has since left.\n\n**Otherwise, three possible answers.** No session redirects to the login page with a\n`redirect` back here. A browser gets the consent page as HTML. `Accept: application/json`\ngets the same information as JSON, for a SPA rendering its own consent screen.\n\nFailures split by whether the redirect URI can be trusted yet. An unknown client, or a\n`redirect_uri` that does not exactly match a registered one, is answered here — never\nredirected, because redirecting would make this an open redirector. Everything after that\n(bad scope, bad PKCE, bad resource) redirects to the client with `error`,\n`error_description`, the `state` if there was one, and always `iss`.\n",
        "x-signclad-extension": true,
        "security": [],
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": false,
            "description": "Required unless the client registered exactly one. Matched exactly, except that the port\nis ignored for loopback addresses so a native app can bind any free port.\n",
            "schema": {
              "type": "string",
              "format": "uri"
            }
          },
          {
            "name": "response_type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "code"
              ]
            }
          },
          {
            "name": "code_challenge",
            "in": "query",
            "required": true,
            "description": "43 characters of unreserved base64url.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9\\-._~]{43}$"
            }
          },
          {
            "name": "code_challenge_method",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "S256"
              ]
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "description": "Space-delimited. Empty means every scope the client registered.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "nonce",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resource",
            "in": "query",
            "required": false,
            "description": "RFC 8707 audience. One absolute `http`/`https` URI, no fragment. Defaults to this API.\n",
            "schema": {
              "type": "string",
              "format": "uri"
            }
          },
          {
            "name": "prompt",
            "in": "query",
            "required": false,
            "description": "A **space-delimited list** (OpenID Connect Core §3.1.2.1), the same production `scope`\nuses. It was compared as a single string until the error-envelope pass, which meant\n`prompt=consent login` silently skipped the very screen the client had insisted on.\n\n- `consent` — force the consent screen even when an existing grant covers the request.\n- `login` — re-authenticate first. The person is sent through the app's sign-in with\n  `prompt=login`, and the URL they return to has `login` removed from its own `prompt`\n  so they are not sent straight back out again.\n- `none` — show nothing. If the request can be answered without interaction it is;\n  otherwise the client is told which interaction it would have needed, at its redirect\n  URI: `login_required` when nobody is signed in, `interaction_required` when consent\n  would have to be shown. This is what lets a silent refresh in a hidden iframe fail\n  fast instead of hanging.\n\n`none` may not be combined with another value — \"ask nothing\" and \"ask for this\" are a\ncontradiction — and doing so is `invalid_request` at the redirect URI. Values that are\nnot implemented (`select_account`) are ignored rather than refused. The supported list\nis advertised as `prompt_values_supported` in the authorization server metadata.\n",
            "schema": {
              "type": "string",
              "examples": [
                "consent",
                "login",
                "none",
                "select_account consent"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The consent screen, as HTML for a browser or JSON for a SPA.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConsentPayload"
                }
              }
            }
          },
          "302": {
            "description": "To the login page when there is no session; straight back to the client with a `code`\nwhen an existing grant covers the request; or back to the client with an `error` when\nthe failure happens after the redirect URI has been validated. Every redirect carries\n`iss`, per RFC 9207, so a client talking to several authorization servers can detect a\nmix-up.\n",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/OAuthError"
          },
          "401": {
            "$ref": "#/components/responses/OAuthUnauthorizedClient"
          },
          "403": {
            "$ref": "#/components/responses/OAuthError"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "tags": [
          "OAuth"
        ],
        "operationId": "decideAuthorizationRequest",
        "summary": "Approve or decline",
        "description": "Records the decision. Every authorization parameter is re-validated from scratch rather\nthan trusted from the session, and `workspace_id` names which workspace the grant is scoped\nto — a person in several workspaces authorizes one of them, not all.\n\nA decline is not an error: it redirects to the client with `error=access_denied`.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/AuthorizationDecision"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AuthorizationDecision"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "For an XHR caller — where to send the browser next.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "redirect_to": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  "required": [
                    "redirect_to"
                  ]
                }
              }
            }
          },
          "302": {
            "description": "Back to the client with `code` and `state`, or with `error=access_denied`.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/OAuthError"
          },
          "401": {
            "$ref": "#/components/responses/OAuthError"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/oauth/token": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "OAuth"
        ],
        "operationId": "issueOAuthToken",
        "summary": "Exchange a grant for a token",
        "description": "Three grants: `authorization_code`, `refresh_token`, and `client_credentials`. Confidential\nclients authenticate with HTTP Basic or with `client_id` and `client_secret` in the body;\npublic clients send `client_id` alone and are refused if they present a secret.\n\n**Refresh tokens rotate and detect reuse.** Using one twice revokes the whole family and\nforces a fresh authorization — the reply says so. A refresh may narrow scope, never widen\nit, and can never change the token's audience.\n\n`refresh_token` is present only when `offline_access` was granted, and never on\n`client_credentials`. `nonce` comes back only when the code carried one.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The token.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                },
                "examples": {
                  "authorizationCode": {
                    "value": {
                      "access_token": "sca_9f8e7d6c5b4a39281706f5e4d3c2b1a0",
                      "token_type": "Bearer",
                      "expires_in": 3600,
                      "scope": "documents:read documents:write offline_access",
                      "refresh_token": "scr_1a2b3c4d5e6f708192a3b4c5d6e7f809"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/OAuthError"
          },
          "401": {
            "$ref": "#/components/responses/OAuthUnauthorizedClient"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/oauth/register": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "OAuth"
        ],
        "operationId": "registerOAuthClient",
        "summary": "Register a client dynamically",
        "description": "RFC 7591 dynamic registration, unauthenticated, so an assistant can connect without anybody\npasting a client id. It issues **public** clients only: `token_endpoint_auth_method` must be\n`none`, and a confidential client for server-to-server work is created in the app instead.\n\nRedirect URIs must be HTTPS, a loopback address, or a reverse-domain private-use scheme. The\nresponse carries a `registration_access_token` and `registration_client_uri` — the only time\nthe token is shown.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientRegistrationRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The registered client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientRegistrationWithToken"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/OAuthError"
          },
          "403": {
            "$ref": "#/components/responses/OAuthError"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/oauth/register/{client_id}": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "parameters": [
        {
          "name": "client_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "OAuth"
        ],
        "operationId": "readOAuthClientRegistration",
        "summary": "Read a dynamic registration",
        "description": "RFC 7592. Authenticated with the `registration_access_token` as a bearer token. Returns the\nsame document as the registration minus the token and the management URI. A client that was\nnot dynamically registered is a `404`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "RegistrationToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The registration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientRegistration"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/OAuthError"
          },
          "404": {
            "$ref": "#/components/responses/OAuthError"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/oauth/revoke": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "OAuth"
        ],
        "operationId": "revokeOAuthToken",
        "summary": "Revoke a token",
        "description": "RFC 7009. Answers `200` with a JSON `null` body whether the token existed, was already\nrevoked, belonged to another client, or was never ours — a revocation endpoint that\ndistinguished those would be a token oracle. `token_type_hint` is accepted and ignored.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "token": {
                    "type": "string"
                  },
                  "token_type_hint": {
                    "type": "string",
                    "description": "Accepted and ignored."
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  }
                },
                "required": [
                  "token"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Revoked, or there was nothing to revoke. The body is the literal `null`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "null"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/OAuthError"
          },
          "401": {
            "$ref": "#/components/responses/OAuthUnauthorizedClient"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/oauth/authorizations": {
      "get": {
        "tags": [
          "Extensions",
          "OAuth"
        ],
        "operationId": "listOAuthAuthorizations",
        "summary": "List connected applications",
        "description": "What this person has approved: which application, which workspace, which scopes, and when it\nwas last used. Not reachable with an OAuth bearer token — no scope grants it, and a\ncredential must not be able to enumerate or revoke credentials.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The authorizations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OAuthAuthorization"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          }
        }
      }
    },
    "/oauth/authorizations/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Extensions",
          "OAuth"
        ],
        "operationId": "revokeOAuthAuthorization",
        "summary": "Disconnect an application",
        "description": "Revokes the grant and every token under it in one transaction. The bearer guard caches\nnothing, so it takes effect on the next request; the sixty seconds in the metadata document\nis a ceiling on a resource server's own introspection cache, not slack here.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "204": {
            "description": "Disconnected."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/oauth/clients": {
      "get": {
        "tags": [
          "Extensions",
          "OAuth"
        ],
        "operationId": "listOAuthClients",
        "summary": "List server-to-server clients",
        "description": "Confidential clients for the client credentials grant. Owner and admin only, and never\nreachable with an OAuth token: a credential must not be able to mint credentials.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The clients.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OAuthClient"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Extensions",
          "OAuth"
        ],
        "operationId": "createOAuthClient",
        "summary": "Create a server-to-server client",
        "description": "Mints a confidential client and returns its secret once. `offline_access` is dropped if\nasked for — a client credentials token has nobody to refresh on behalf of. Omitting `scopes`\ngives `documents:read`, `documents:write`, and `templates:read`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "scopes": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "$ref": "#/components/schemas/OAuthScope"
                    }
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The client, with its secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthClientWithSecret"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/oauth/clients/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Extensions",
          "OAuth"
        ],
        "operationId": "deleteOAuthClient",
        "summary": "Delete a server-to-server client",
        "description": "Revokes the client and its tokens.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/files/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The file's id.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Files"
        ],
        "operationId": "getFile",
        "summary": "Get a file",
        "description": "Ingest state and a preview link for one uploaded file. A file in another workspace is a\n`404`, indistinguishable from one that does not exist.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadedFile"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Extensions",
          "Files"
        ],
        "operationId": "deleteFile",
        "summary": "Release an uploaded file",
        "description": "Queues deletion of an upload and all of its stored derivatives when no live document,\ntemplate, attachment request, or application references it. Referenced files are retained.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "204": {
            "description": "Cleanup was accepted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/files/{id}/stored": {
      "delete": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "deleteStoredFile",
        "summary": "Delete an unused stored file",
        "description": "Uses the same visibility rules as the stored-file picker. Returns 409 if\nany document, template, attachment, reusable asset, or saved history still\nreferences the file, including references outside the current workspace.\nOtherwise tombstones the row and queues retryable deletion of the original\nand derivatives on the recorded storage disk. A 204 confirms acceptance,\nnot reclaimed bytes. Lost cleanup jobs are recovered by files:cleanup-unused.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Queued for deletion. A `204` confirms acceptance, not reclaimed bytes: the objects\nthemselves go on a retryable job.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/files/{id}/content": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Files"
        ],
        "operationId": "getFileContent",
        "summary": "Download a file's normalized PDF",
        "description": "The normalized PDF bytes, inline, for a viewer. This is what `preview_url` points at: the\nsame route carrying `expires` and `signature`, so a rendered page can be fetched without\nputting an API key in a URL. Present either parameter and the signature must verify.\n\nAn expired or tampered link is a `404` whose `meta.message` reads\n`This preview link has expired.` — a distinct message on an otherwise identical body.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "expires",
            "in": "query",
            "required": false,
            "description": "From `preview_url`. Part of what the signature covers.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "signature",
            "in": "query",
            "required": false,
            "description": "From `preview_url`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The PDF bytes, `inline` with the file's sanitized name.",
            "headers": {
              "Content-Disposition": {
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `private, no-store`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No such file, the bytes are gone, or the preview link has expired. The expired case is\nthe only one with its own `meta.message`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                },
                "examples": {
                  "expired": {
                    "value": {
                      "message": "Not found",
                      "meta": {
                        "error": "record_not_found",
                        "message": "This preview link has expired.",
                        "messages": [
                          "This preview link has expired."
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/t/{token}": {
      "parameters": [
        {
          "name": "token",
          "in": "path",
          "required": true,
          "description": "The link token. Deliberately the shortest path in v1 — this URL gets printed, pasted, and typed.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Template links"
        ],
        "operationId": "getPublicIntake",
        "summary": "Read a public fill link",
        "description": "What a stranger holding the link is allowed to see before they start: whose form it is, what\nrole they are filling, and what the page must ask them for. Unauthenticated — the token is\nthe credential.\n\nDeliberately absent: the template id, the workspace id, the field list, the other\nrecipients, and any metadata. A link is permission to start a document, not to read one.\n\nEvery failure — unknown token, disabled link, expired link, a single-use link already spent,\na template that is no longer available — is the same `404`, with `meta.message` reading\n`Couldn't find the page requested`.\n",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "200": {
            "description": "What the intake page needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicIntake"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/PlanRestricted"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/files/{id}/retry": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The file's id.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Files"
        ],
        "operationId": "retryFile",
        "summary": "Retry file preparation",
        "description": "Requeues the stored original after a retryable malware-scanner failure. Known malware,\nconversion failures, and files whose original is missing must be replaced instead.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The file was returned to its pending preparation state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadedFile"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/t/{token}/start": {
      "parameters": [
        {
          "name": "token",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Template links"
        ],
        "operationId": "startPublicIntake",
        "summary": "Start a document from a public link",
        "description": "Creates the document and returns a signing session for the person who just filled the form,\nso the page can go straight into signing without an email round trip.\n\nTwo limiters apply: the shared 50/min unauthenticated bucket protects Signclad, and a\nper-token limiter protects one customer's document allowance from one attacker who found\ntheir link. `name` and `email` are required only when the link says so — read\n`require_name_email` from `GET /t/{token}` rather than assuming.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "email",
                    "maxLength": 255
                  },
                  "captcha_token": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 4000,
                    "description": "Required when `captcha_required` is set on the link."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new document and a session to sign it with.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicIntakeStart"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "`errors.captcha_token` reads `The captcha could not be verified. Please try again.`;\n`errors.name` reads `Your name is required.`; `errors.email` reads\n`Your email address is required.` A plan without template links answers\n`not_available_on_plan`, checked against the template's workspace rather than the\ncaller's — a stranger has none.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/template_links/{template}": {
      "parameters": [
        {
          "name": "template",
          "in": "path",
          "required": true,
          "description": "The template's id.",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Template links"
        ],
        "operationId": "getTemplateLinkSettings",
        "summary": "Get a template's link settings",
        "description": "The public URL and the rules behind it. A link exists from the moment the template does; it\nis `enabled: false` until somebody turns it on.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "templates:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The link and its settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateLink"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/PlanRestricted"
          }
        }
      },
      "put": {
        "tags": [
          "Extensions",
          "Template links"
        ],
        "operationId": "updateTemplateLinkSettings",
        "summary": "Update a template's link settings",
        "description": "A partial merge despite being a `PUT`: only the keys present change. `redirect_url` must be\nHTTPS. An `expires_at` in the past is accepted — it simply makes the link unusable, which is\na reasonable way to switch one off.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TemplateLinkSettings"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated link.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateLink"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/template_links/{template}/rotate": {
      "parameters": [
        {
          "name": "template",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Template links"
        ],
        "operationId": "rotateTemplateLink",
        "summary": "Rotate a template's link",
        "description": "Issues a new token and kills the old URL immediately. This is the answer to a link that\nleaked: the settings survive, the address does not.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The link, at its new address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateLink"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/PlanRestricted"
          }
        }
      }
    },
    "/api_applications": {
      "get": {
        "tags": [
          "Extensions",
          "API applications"
        ],
        "operationId": "listApiApplications",
        "summary": "List API applications",
        "description": "Every application in the workspace, newest first. A real cursor page like the other\nlists: follow `next_cursor` while `has_more` is true. Workspaces hold a handful of\napplications, so a second page is rare — but a client that follows the cursor gets one\nrather than looping.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/CursorLimit"
          }
        ],
        "responses": {
          "200": {
            "description": "The applications.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiApplication"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Extensions",
          "API applications"
        ],
        "operationId": "createApiApplication",
        "summary": "Create an API application",
        "description": "Branding and embed configuration for a partner integration. `callback_urls` and\n`allowed_embed_domains` are **origins** — scheme and host, no path — and are normalized on\nwrite; setting `callback_urls` without mentioning `allowed_embed_domains` keeps the two in\nstep.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApiApplicationWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The application.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiApplication"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/api_applications/{id}/logo": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiApplicationId"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "API applications"
        ],
        "operationId": "uploadApiApplicationLogo",
        "summary": "Upload an application logo",
        "description": "PNG, JPEG, WebP, or GIF, up to 2 MB. SVG is deliberately refused — it is a script vector,\nand this image is rendered inside other people's pages.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/LogoUpload"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The application, with its new logo URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiApplication"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      },
      "delete": {
        "tags": [
          "Extensions",
          "API applications"
        ],
        "operationId": "deleteApiApplicationLogo",
        "summary": "Remove an application logo",
        "description": "Falls back to the workspace's logo, then to none.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The application, without a logo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiApplication"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/workspace/branding": {
      "get": {
        "tags": [
          "Extensions",
          "Branding"
        ],
        "operationId": "getWorkspaceBranding",
        "summary": "Get workspace branding",
        "description": "The layer under an API application: the logo, the \"from\" name, the email signature, the\ncolours, and the sending defaults an omitted create-body key inherits.\n\n`resolved` is what an embedded page or an email will actually use once the API application,\nthe workspace, and the built-in defaults have been resolved in that order — and `source`\nsays which of the three won. The configured half is nullable; `resolved` never is.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "account:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The branding.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceBranding"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "patch": {
        "tags": [
          "Extensions",
          "Branding"
        ],
        "operationId": "updateWorkspaceBranding",
        "summary": "Update workspace branding",
        "description": "Only the keys present change; a key present and `null` clears it. `email_signature` is\nsanitized on the way in, so what comes back may not be byte-identical to what was sent.\nRequires `manage_settings`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkspaceBrandingWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated branding.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceBranding"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/workspace/branding/logo": {
      "post": {
        "tags": [
          "Extensions",
          "Branding"
        ],
        "operationId": "uploadWorkspaceLogo",
        "summary": "Upload the workspace logo",
        "description": "The same rules as an application logo — PNG, JPEG, WebP, or GIF, up to 2 MB, and no SVG.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/LogoUpload"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The branding, with its new logo URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceBranding"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/abuse/report": {
      "post": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "reportAbuse",
        "summary": "Report a document",
        "description": "Public, unauthenticated: the reporter is a stranger on a signing page who thinks the\ndocument is phishing. Always answers `202` with the same message whether or not the document\nid resolved — a report endpoint that confirmed which ids exist would be a lookup service.\n\nRate limited per document as well as per IP, and the throttle fires before the captcha, so a\nflood never burns captcha tokens.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "document_id": {
                    "type": "string",
                    "maxLength": 64
                  },
                  "reason": {
                    "type": "string",
                    "enum": [
                      "phishing",
                      "spam",
                      "not_me",
                      "content",
                      "other"
                    ]
                  },
                  "detail": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "reporter_email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "email",
                    "maxLength": 255
                  },
                  "captcha_token": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 4096
                  }
                },
                "required": [
                  "document_id",
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Received. The same answer for an id that does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "received": {
                      "type": "boolean",
                      "const": true
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "received"
                  ]
                }
              }
            }
          },
          "422": {
            "description": "`errors.captcha_token` reads `The captcha could not be verified. Please try again.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/inbound/stripe": {
      "post": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "ingestStripeEvent",
        "summary": "Stripe webhook",
        "description": "Internal. Billing events from Stripe, authenticated by `Stripe-Signature` over the raw body.\nDeduplicated on Stripe's event id, so a redelivery is recognized rather than applied twice.\n\nA handler that throws is deliberately **not** caught: a `500` makes Stripe retry, which is\nwhat should happen when a webhook cannot be processed yet. The two client errors have their\nown bare bodies rather than the shared envelope.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Processed, or recognized as a duplicate. `type` is absent on a duplicate.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "received": {
                      "type": "boolean"
                    },
                    "duplicate": {
                      "type": "boolean"
                    },
                    "type": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "received",
                    "duplicate"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The signature is missing or does not verify. The body says nothing more, by design.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "No webhook secret is configured in this environment, so nothing is accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/signer/reauthentication": {
      "post": {
        "tags": [
          "Signer"
        ],
        "operationId": "startSignerReauthentication",
        "summary": "Start Part 11 re-authentication",
        "description": "The second identification component 21 CFR Part 11 §11.200(a)(1) requires: having opened the\ndocument with a link, the signer proves who they are again before signing. `start` issues a\nchallenge — an emailed code, or the account password when the signer is a Signclad user —\nand `verify` answers it.\n\nIn `streamlined` mode one challenge covers the session. In `per_signature` mode\n`field_api_id` is required and names the signature or initials field about to be signed.\n\nThe Part 11 check runs before validation, so a document that is not in Part 11 mode answers\n`403 not_a_cfr_part11_document` even for a malformed body.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "SignerSession": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "field_api_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120,
                    "description": "Required in `per_signature` mode, ignored in `streamlined`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The challenge. Keys that do not apply are **absent**: `field_api_id` in streamlined\nmode, and `code` unless this is a test-mode document, where the code is returned so a\ntest can complete without reading email.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReauthenticationChallenge"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`meta.error` is `not_a_cfr_part11_document`, `reauthentication_unavailable` (a\nrecipient with no email address and no account), `reauthentication_required` (no or unknown\n`field_api_id` in per-signature mode, with `meta.fields` listing what is acceptable), or\n`scope_not_allowed`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignerError"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/SignerConflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/signer/reauthentication/verify": {
      "post": {
        "tags": [
          "Signer"
        ],
        "operationId": "verifySignerReauthentication",
        "summary": "Answer a Part 11 re-authentication challenge",
        "description": "Send the emailed `code`, or the `password` when the challenge asked for one. The reply\ncarries a `challenge_token` to present with the signature.\n\nA wrong code and an unknown challenge id answer identically — `reauthentication_invalid` —\nso a wrong guess reveals nothing about which ids exist. Running out of attempts reads as\n`reauthentication_expired`, the same as running out of time.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "SignerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "id": {
                    "type": "string",
                    "maxLength": 64
                  },
                  "code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 32
                  },
                  "password": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  }
                },
                "required": [
                  "id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verified.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReauthenticationResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`meta.error` is `reauthentication_invalid` (unknown id, already used, or wrong answer),\n`reauthentication_expired` (out of time or out of attempts), or\n`not_a_cfr_part11_document`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignerError"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/SignerConflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/signer/id_verification/start": {
      "post": {
        "tags": [
          "Signer"
        ],
        "operationId": "startIdVerification",
        "summary": "Start identity verification",
        "description": "Returns the provider's hosted URL to send the signer to. No request body: what is being\nverified is already fixed by the document.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "SignerSession": []
          }
        ],
        "responses": {
          "201": {
            "description": "The check, and where to send the signer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdVerificationCheck"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`meta.error` is `id_verification_not_requested` when this document and recipient do not\ncall for it, or `scope_not_allowed`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignerError"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/SignerConflict"
          },
          "503": {
            "$ref": "#/components/responses/ProviderNotConfigured"
          }
        }
      }
    },
    "/signer/id_verification/complete": {
      "post": {
        "tags": [
          "Signer"
        ],
        "operationId": "completeIdVerification",
        "summary": "Read back an identity verification",
        "description": "Reads the outcome from the provider. It **never accepts an outcome from the signer** — that\nwould make the check worthless — and it is idempotent: an already-verified check is returned\nwithout another provider call.\n\nThe submit gate is separate: `POST /signer/submit` answers\n`403 id_verification_incomplete` while a required check has not passed.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "SignerSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "The check as it now stands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdVerificationCheck"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`meta.error` is `id_verification_required` when no check was ever started, or `scope_not_allowed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignerError"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/SignerConflict"
          },
          "503": {
            "$ref": "#/components/responses/ProviderNotConfigured"
          }
        }
      }
    },
    "/admin/lookup": {
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminLookup",
        "summary": "Find an account",
        "description": "Staff only. One search box over workspace ids, account ids, document ids, API key prefixes,\nemails, and names; `matched_by` says which one hit. At most 20 results, and the search itself\nis written to the staff audit log.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Anything identifying. An empty query returns an empty list.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The matches.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "maxItems": 20,
                      "items": {
                        "$ref": "#/components/schemas/AdminLookupResult"
                      }
                    },
                    "query": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/accounts/{account}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminAccountId"
        }
      ],
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminGetAccount",
        "summary": "Get an account overview",
        "description": "Staff only. The workspace, its billing account, the current period's usage, the counts a\nsupport engineer asks for first, and the abuse reputation score.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The overview.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminAccountOverview"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/accounts/{account}/members": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminAccountId"
        }
      ],
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminListAccountMembers",
        "summary": "List an account's members",
        "description": "Staff only. Archived memberships included, with whether each has MFA enrolled.",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The members.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminMember"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/accounts/{account}/keys": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminAccountId"
        }
      ],
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminListAccountKeys",
        "summary": "List an account's API keys",
        "description": "Staff only. Prefixes and usage, never a secret or a hash — this view answers \"is their key\nworking\", not \"what is their key\".\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The keys.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminApiKey"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/accounts/{account}/webhooks": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminAccountId"
        }
      ],
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminListAccountWebhooks",
        "summary": "List an account's webhook endpoints",
        "description": "Staff only. Health and the last failure, for \"why are their webhooks not arriving\".",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminWebhook"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/accounts/{account}/documents": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminAccountId"
        }
      ],
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminListAccountDocuments",
        "summary": "List an account's documents",
        "description": "Staff only. A metadata-only listing — names, states, counts, error messages. No fields, no\nrecipients' values, no file contents.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "state",
            "in": "query",
            "required": false,
            "description": "Internal state, not the wire status.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/CursorLimit"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of documents.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminDocument"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/accounts/{account}/audit": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminAccountId"
        }
      ],
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminListAccountAudit",
        "summary": "Read an account's audit log",
        "description": "Staff only. The same entries the customer can read, flattened.",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of entries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminAuditEntry"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/accounts/{account}/plan": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminAccountId"
        }
      ],
      "patch": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminUpdateAccountPlan",
        "summary": "Override an account's plan",
        "description": "Staff only. A `reason` of at least eight characters is required and is written to the staff\naudit log — an override nobody can explain later is not an override worth having.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "plan": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "interval": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "monthly",
                      "yearly",
                      null
                    ]
                  },
                  "reason": {
                    "$ref": "#/components/schemas/StaffReason"
                  }
                },
                "required": [
                  "plan",
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The account overview, after the change.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminAccountOverview"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/accounts/{account}/unban": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminAccountId"
        }
      ],
      "post": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminUnbanAccount",
        "summary": "Lift a suspension",
        "description": "Staff only. Requires a reason, which is audited.",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "reason": {
                    "$ref": "#/components/schemas/StaffReason"
                  }
                },
                "required": [
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The account overview.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminAccountOverview"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/accounts/{account}/refunds": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminAccountId"
        }
      ],
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminListRefunds",
        "summary": "List refunds",
        "description": "Staff only. The 25 most recent refunds on this account.",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The refunds.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminRefund"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminIssueRefund",
        "summary": "Issue a refund",
        "description": "Staff only. `target` is a Stripe invoice or charge id — one starting `in_` is read as an\ninvoice. Over the configured ceiling the refund is refused and must be issued in Stripe with\na second approver.\n\nA gateway failure still answers `200`: the refund row exists with `status: failed` and a\n`failure_message`, because the attempt is part of the record.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "target": {
                    "type": "string",
                    "maxLength": 64
                  },
                  "amount_cents": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3,
                    "default": "usd"
                  },
                  "reason": {
                    "$ref": "#/components/schemas/StaffReason"
                  }
                },
                "required": [
                  "target",
                  "amount_cents",
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The refund — successful or failed — and the account's refund history.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "refund": {
                      "$ref": "#/components/schemas/AdminRefund"
                    },
                    "history": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminRefund"
                      }
                    }
                  },
                  "required": [
                    "refund"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/documents/{document}/cancel": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminDocumentId"
        }
      ],
      "post": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminCancelDocument",
        "summary": "Cancel a document",
        "description": "Staff only, with an audited reason. Crosses workspaces, which is the point.",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffReasonBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminDocumentState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/documents/{document}/complete": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminDocumentId"
        }
      ],
      "post": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminCompleteDocument",
        "summary": "Manually complete a document",
        "description": "Staff only, with an audited reason.",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffReasonBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminDocumentState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/documents/{document}/release": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminDocumentId"
        }
      ],
      "post": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminReleaseDocument",
        "summary": "Release a blocked document",
        "description": "Staff only. Lets a document held for abuse review carry on, optionally naming which state to\nrestore it to.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "reason": {
                    "$ref": "#/components/schemas/StaffReason"
                  },
                  "restore_to": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "sent",
                      "viewed",
                      "pending",
                      null
                    ]
                  }
                },
                "required": [
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminDocumentState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/documents/{document}/resend": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminDocumentId"
        }
      ],
      "post": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminResendDocument",
        "summary": "Resend a document's invitations",
        "description": "Staff only. Optionally to named addresses; omitted, it goes to everyone still unsigned. The\none admin write that takes no `reason`, because resending changes nothing.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "emails": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "email"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The document, and who it went to.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/AdminDocumentState"
                    },
                    {
                      "type": "object",
                      "additionalProperties": true,
                      "properties": {
                        "resent_to": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "format": "email"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/impersonate/{user}": {
      "parameters": [
        {
          "name": "user",
          "in": "path",
          "required": true,
          "description": "The user to act as.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminStartImpersonation",
        "summary": "Start impersonating a user",
        "description": "Staff only. Staff accounts cannot be impersonated, and neither can yourself.\n\n**The body is optional and so is `reason`.** The console sends neither; where no reason is\ngiven the server records one of its own naming the staff member and the console, so every\ngrant row and audit entry stays readable rather than carrying an empty string. Send a\nreason when you have a better one than that.\n\nThe session runs until `POST /admin/impersonate/stop`, or until the grant is ended from\nanother session. There is no expiry.\n\nWhile a grant is live the admin surface itself answers `404` — an impersonating session is\nthe customer, not staff.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500,
                    "description": "Why the session is being started. Optional, with no minimum length — unlike the\nother staff actions, which insist on one.\n"
                  },
                  "ticket": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The grant.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "impersonation": {
                      "$ref": "#/components/schemas/Impersonation"
                    }
                  },
                  "required": [
                    "impersonation"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "`errors.user` reads `Staff accounts cannot be impersonated.` or\n`You are already signed in as yourself.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/impersonate/stop": {
      "post": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminStopImpersonation",
        "summary": "Stop impersonating",
        "description": "Hands the session back to the staff user. `stopped: false` means there was nothing to stop,\nwhich is not an error. This is the one admin route reachable while a grant is live.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "Stopped, or there was nothing to stop.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "stopped": {
                      "type": "boolean"
                    },
                    "impersonation": {
                      "type": "null"
                    }
                  },
                  "required": [
                    "stopped"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/abuse/queue": {
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminListAbuseQueue",
        "summary": "Read the abuse review queue",
        "description": "Staff only. Reports from signers, from the scorer, and from staff, with the workspace and\ndocument each concerns. An unrecognized `status` filter is quietly treated as `open` rather\nthan refused.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "open",
                "released",
                "canceled",
                "banned",
                "dismissed"
              ],
              "default": "open"
            }
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/CursorLimit"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of reports, and a tally by status. The tally omits statuses with no rows rather\nthan reporting them as zero.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AbuseReport"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "counts": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/abuse/{report}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AbuseReportId"
        }
      ],
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminGetAbuseReport",
        "summary": "Get an abuse report",
        "description": "Staff only. The report, plus up to ten other reports against the same workspace.",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The report.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AbuseReportDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/abuse/{report}/release": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AbuseReportId"
        }
      ],
      "post": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminReleaseAbuseReport",
        "summary": "Release a reported document",
        "description": "Staff only. Decides the report was unfounded and lets the document carry on. Only an open\nreport can be decided; one already reviewed answers `409` naming who reviewed it, and that\ncheck runs before the body is validated.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffReasonBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The decided report.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AbuseReportDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/abuse/{report}/cancel": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AbuseReportId"
        }
      ],
      "post": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminCancelAbuseReport",
        "summary": "Cancel the reported document",
        "description": "Staff only. Cancels the document but leaves the workspace alone.",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffReasonBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The decided report.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AbuseReportDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/abuse/{report}/ban": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AbuseReportId"
        }
      ],
      "post": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminBanFromAbuseReport",
        "summary": "Suspend the workspace",
        "description": "Staff only. Suspends the whole workspace, not just the document. `POST\n/admin/accounts/{account}/unban` is the way back.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffReasonBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The decided report.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AbuseReportDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/sso/config": {
      "get": {
        "tags": [
          "Extensions",
          "Accounts"
        ],
        "operationId": "getSsoConfig",
        "summary": "Get the SSO configuration",
        "description": "SAML or OIDC single sign-on for this workspace, plus the service-provider values an IdP\nadministrator needs to paste into their end. Owner only — an API key resolves to `admin`\nand is always refused — and Enterprise only.\n\nTwo shapes, told apart by `configured`. Before anything is set up the body is just\n`configured: false` and `service_provider`, which is exactly what somebody starting the\nsetup needs.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/SsoConfigUnconfigured"
                    },
                    {
                      "$ref": "#/components/schemas/SsoConfig"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/PlanRestricted"
          }
        }
      },
      "put": {
        "tags": [
          "Extensions",
          "Accounts"
        ],
        "operationId": "updateSsoConfig",
        "summary": "Update the SSO configuration",
        "description": "**A merge, not a replacement**, despite the verb: a field left out is untouched, and a field\nsent empty is cleared. `protocol` is required the first time.\n\nThe request is flat where the response nests: `idp_entity_id` and friends come back under\n`saml`, `oidc_issuer` and `oidc_client_id` under `oidc`, and `jit_enabled`, `default_role`,\nand `group_role_map` under `provisioning`. `oidc_client_secret` is write-only; the response\nonly says whether one is set.\n\nSending `metadata_url` makes the server fetch that document and overwrite the SAML fields\nfrom it, which is the fast path for an IdP that publishes metadata.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SsoConfigWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The configuration, as stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SsoConfig"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.protocol` when the first write names none. Everything the connection itself\nrejects arrives under **`errors.sso`** — an unreadable certificate, a `default_role` of\n`owner`, a group map granting a role that cannot be provisioned, enforcing SSO on a\nconnection that cannot be used, or a metadata URL that could not be fetched or parsed.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                },
                "examples": {
                  "badCertificate": {
                    "value": {
                      "errors": {
                        "sso": "The IdP certificate is not a readable X.509 certificate."
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/sso/metadata": {
      "get": {
        "tags": [
          "Extensions",
          "Accounts"
        ],
        "operationId": "getSsoServiceProviderMetadata",
        "summary": "Download SAML SP metadata",
        "description": "The service-provider metadata document to hand to an IdP administrator, as XML. Owner only,\nEnterprise only. A workspace with no usable connection answers `422` — JSON, on an endpoint\nthat otherwise returns XML.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The SP metadata, as a downloadable XML file.",
            "headers": {
              "Content-Disposition": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/samlmetadata+xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.sso` reads `This workspace has no usable SSO connection.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/sso/scim/token": {
      "post": {
        "tags": [
          "Extensions",
          "Accounts"
        ],
        "operationId": "rotateScimToken",
        "summary": "Issue a SCIM token",
        "description": "Mints the bearer token an IdP uses to provision users, and returns it once. Issuing a new\none invalidates the old.\n\nThe token **is** the tenant: SCIM calls carry no user and no role, so this value is the whole\ncredential for the provisioning surface.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "201": {
            "description": "The token, shown once.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "token": {
                      "type": "string"
                    },
                    "endpoint": {
                      "type": "string",
                      "format": "uri"
                    },
                    "created_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "token",
                    "endpoint"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.sso` reads `This workspace has no usable SSO connection.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/workspace/compatibility": {
      "get": {
        "tags": [
          "Extensions",
          "Accounts"
        ],
        "operationId": "getWorkspaceCompatibility",
        "summary": "Get compatibility shim status",
        "description": "Whether this workspace still emits the compatibility `SW-*` message aliases in embedded\npages, and how often anything used them in the last 30 days. `recorded: false` means nothing\nhas been measured yet, which is not the same as zero — the difference decides whether it is\nsafe to turn the shim off.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "account:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The shim's status and usage.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "alias_usage_30d": {
                      "type": "integer"
                    },
                    "recorded": {
                      "type": "boolean"
                    },
                    "updated_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/workspace/compliance": {
      "get": {
        "tags": [
          "Extensions",
          "Accounts"
        ],
        "operationId": "getWorkspaceCompliance",
        "summary": "Get compliance settings",
        "description": "The workspace's 21 CFR Part 11, NOM-151, and identity-verification switches, what the plan\nmakes available, and the values each setting accepts. Readable by any member.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "account:read"
            ]
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComplianceSettings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "patch": {
        "tags": [
          "Extensions",
          "Accounts"
        ],
        "operationId": "updateWorkspaceCompliance",
        "summary": "Update compliance settings",
        "description": "Owner only. Turning a feature **off** is always allowed, even on a plan that no longer\ncarries it — a workspace must be able to stop doing something it can no longer pay for.\nTurning one on without the plan is `422`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ComplianceSettingsWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComplianceSettings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "Either the ordinary validation envelope, or — when the plan does not carry the feature\nbeing switched on — a bare `{\"message\": \"That compliance feature is not available on\nthis plan.\"}`. This route predates the shared plan-gate envelope and does not use it.\n",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ValidationErrorBody"
                    },
                    {
                      "type": "object",
                      "additionalProperties": true,
                      "properties": {
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/workspaces/{id}/storage_region": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Accounts"
        ],
        "operationId": "changeStorageRegion",
        "summary": "Move a workspace's files to another region",
        "description": "Queues a migration of every stored object to the named region. Owner only, and any region\nother than the default needs the `eu_residency` feature.\n\n`202` when a move was queued, `200` when the workspace is already there — same body,\ndistinguished by `changed`. `storage_region` keeps reporting the **old** region until the\nmigration completes; `effective_region` is where a write would land right now, which can\ndiffer again if the plan lapsed. Only one migration at a time: a second is `409`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "storage_region": {
                    "type": "string",
                    "maxLength": 8,
                    "description": "Currently `us` or `eu`. The list is deployment configuration.",
                    "examples": [
                      "eu"
                    ]
                  }
                },
                "required": [
                  "storage_region"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already in that region. Nothing was queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegionChange"
                }
              }
            }
          },
          "202": {
            "description": "The migration was queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegionChange"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "A migration is already running. `errors.base.region_change_in_progress`, with the\nmigration in `meta.migration`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "422": {
            "description": "`errors.storage_region` names the regions on offer for an unknown one; a region the plan\ndoes not carry is `errors.base.not_available_on_plan` with `meta.feature: eu_residency`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/api_keys/{id}/requests": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Accounts"
        ],
        "operationId": "listApiKeyRequests",
        "summary": "Read a key's request log",
        "description": "The last seven days of calls made with this key: method, path, status, latency, request id,\nand a ready-made `copy_as_curl`. Bodies are never stored and query strings are redacted at\nwrite time, so a secret in a URL does not become a second copy of the secret.\n\nRequires `manage_settings`. `status` accepts a class (`4`) or an exact code (`404`); a value\nthat is neither, and an unparseable `since`, are ignored rather than refused.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "`4` for every 4xx, `404` for exactly that. Anything else is ignored.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of requests.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiRequestLogEntry"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "retention_days": {
                      "type": "integer",
                      "description": "How long these rows are kept. Seven days."
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/sso/{workspace_slug}/login": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "parameters": [
        {
          "$ref": "#/components/parameters/WorkspaceSlug"
        }
      ],
      "get": {
        "tags": [
          "Authentication"
        ],
        "operationId": "startSsoLogin",
        "summary": "Begin SSO sign-in",
        "description": "Redirects to the IdP — a SAML `AuthnRequest`, or the OIDC authorization endpoint. Nothing\nhere is authenticated; that is the point.\n\nThere are no error bodies on this flow. Every failure redirects to the web app's login page\nwith `?sso_error=<reason>`, and the reasons are `not_configured`, `not_available_on_plan`,\n`invalid_assertion`, `assertion_replayed`, `unknown_state`, `domain_not_allowed`, `no_email`,\n`no_membership`, `provider_unreachable`, `invalid_id_token`, and `provider_refused`.\n\nAn unknown slug, a plan without SSO, a missing connection, and an unusable one all answer\nthe same `404`, so nothing here reveals which workspaces exist or how they are configured.\n",
        "x-signclad-extension": true,
        "security": [],
        "parameters": [
          {
            "name": "return_to",
            "in": "query",
            "required": false,
            "description": "Where to land after signing in. Must be a site-relative path; anything else — including a\nprotocol-relative `//host` — is dropped silently rather than refused.\n",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "To the IdP, or to the login page with an `sso_error`.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/sso/{workspace_slug}/acs": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "parameters": [
        {
          "$ref": "#/components/parameters/WorkspaceSlug"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "consumeSamlAssertion",
        "summary": "SAML assertion consumer",
        "description": "Where the IdP posts its `SAMLResponse`. The XML signature is the credential, so there is no\nsession, no bearer token, and no CSRF token on this route.\n\nFive things are checked: the signature, the audience, `InResponseTo`, replay, and whether the\nasserted address is in an allowed domain. Success sets the session cookie and redirects into\nthe app; every failure redirects to the login page with `?sso_error=<reason>`.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "SAMLResponse": {
                    "type": "string",
                    "contentEncoding": "base64"
                  },
                  "RelayState": {
                    "type": "string",
                    "description": "Honoured only on an IdP-initiated sign-in."
                  }
                },
                "required": [
                  "SAMLResponse"
                ]
              }
            }
          }
        },
        "responses": {
          "302": {
            "description": "Into the app on success, or to the login page with an `sso_error`.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/sso/{workspace_slug}/callback": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "parameters": [
        {
          "$ref": "#/components/parameters/WorkspaceSlug"
        }
      ],
      "get": {
        "tags": [
          "Authentication"
        ],
        "operationId": "consumeOidcCallback",
        "summary": "OIDC redirect target",
        "description": "Where the OIDC provider sends the browser back. The stored `state` row, the PKCE verifier,\nand the `nonce` are what bind the response to the request. Same redirect-only error model as\nthe SAML routes.\n",
        "x-signclad-extension": true,
        "security": [],
        "parameters": [
          {
            "name": "code",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "error",
            "in": "query",
            "required": false,
            "description": "Set by the provider when the person declined or it refused.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Into the app on success, or to the login page with an `sso_error`.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/sso/{workspace_slug}/slo": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "parameters": [
        {
          "$ref": "#/components/parameters/WorkspaceSlug"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "startSsoLogout",
        "summary": "Sign out of SSO",
        "description": "Clears the Signclad session and hands back the IdP's logout URL for the browser to visit,\nwhen the connection publishes one. Answers `200` with the same body whether or not there was\na session to clear.\n\nThe one SSO route that returns JSON rather than a redirect — and, unlike the assertion\nconsumer, it is CSRF-protected, so a SPA must send the token. It does not accept an\nIdP-initiated `LogoutRequest`; there is no `GET`.\n",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "200": {
            "description": "Signed out. `idp_logout_url` is `null` when the IdP publishes none.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "idp_logout_url": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uri"
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/sso/{workspace_slug}/metadata": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "parameters": [
        {
          "$ref": "#/components/parameters/WorkspaceSlug"
        }
      ],
      "get": {
        "tags": [
          "Authentication"
        ],
        "operationId": "getPublicSsoMetadata",
        "summary": "Public SAML SP metadata",
        "description": "The same service-provider metadata as `GET /sso/metadata`, at a public URL an IdP can fetch\nfor itself. Cached five minutes. Every failure — unknown slug, unentitled plan, no\nconnection, metadata that will not validate — is one undifferentiated `404`.\n",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "200": {
            "description": "The SP metadata.",
            "content": {
              "application/samlmetadata+xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/scim/v2/Users": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Accounts"
        ],
        "operationId": "scimListUsers",
        "summary": "SCIM — list users",
        "description": "RFC 7644 provisioning. The SCIM bearer token from `POST /sso/scim/token` is the whole\ncredential and identifies the workspace; there is no user and no role on this surface, and\nevery response — errors included — is `application/scim+json` with the SCIM error envelope\nrather than Signclad's.\n\nOnly `userName eq \"value\"` is supported as a filter. `sortBy` and `sortOrder` are ignored\nrather than refused.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ScimToken": []
          }
        ],
        "parameters": [
          {
            "name": "filter",
            "in": "query",
            "required": false,
            "description": "Only `userName eq \"value\"`. Anything else is `400 invalidFilter`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "startIndex",
            "in": "query",
            "required": false,
            "description": "1-based.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Page size. `0` returns no rows and only `totalResults`, which is how a provider sizes a\nsync before running one.\n",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of users.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ScimError"
          },
          "401": {
            "$ref": "#/components/responses/ScimError"
          },
          "403": {
            "$ref": "#/components/responses/ScimError"
          }
        }
      },
      "post": {
        "tags": [
          "Extensions",
          "Accounts"
        ],
        "operationId": "scimCreateUser",
        "summary": "SCIM — create a user",
        "description": "Provisions a membership. The body is read leniently, as IdPs vary: the address comes from\n`userName` or the first valid `emails[].value`, the display name from `displayName`,\n`name.formatted`, the given and family names, or the address; everything else in the payload\nis ignored without complaint.\n\n`roles[].value` is honoured when it is a real role, except `owner`, which is quietly\ndowngraded to `admin` — ownership is not something an IdP gets to grant. An address outside\nthe connection's allowed domains is refused.\n\n`externalId` is the directory's own immutable handle for the person. It is stored verbatim\nagainst the membership and echoed back on every read. It used to be discarded, and the\nresponse answered Signclad's user id instead — which meant a provider that stored and\nreconciled on it was matching against an identifier it never issued.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ScimToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/scim+json": {
              "schema": {
                "$ref": "#/components/schemas/ScimUser"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScimUser"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The provisioned user.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            },
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimUser"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ScimError"
          },
          "401": {
            "$ref": "#/components/responses/ScimError"
          },
          "403": {
            "$ref": "#/components/responses/ScimError"
          },
          "409": {
            "$ref": "#/components/responses/ScimError"
          }
        }
      }
    },
    "/scim/v2/Users/{id}": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The membership id, which is what SCIM `id` means here.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Accounts"
        ],
        "operationId": "scimGetUser",
        "summary": "SCIM — get a user",
        "description": "A membership outside the token's workspace is `404`, like one that does not exist.",
        "x-signclad-extension": true,
        "security": [
          {
            "ScimToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The user.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimUser"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/ScimError"
          },
          "403": {
            "$ref": "#/components/responses/ScimError"
          },
          "404": {
            "$ref": "#/components/responses/ScimError"
          }
        }
      },
      "patch": {
        "tags": [
          "Extensions",
          "Accounts"
        ],
        "operationId": "scimPatchUser",
        "summary": "SCIM — deactivate or reactivate a user",
        "description": "**Only `active` can change.** Role, name, and address are not patchable, and asking to patch\none is a `400` rather than a silent no-op — a provisioning system that thinks it renamed\nsomebody should find out.\n\n`Operations` and `operations` are both accepted, `replace` and `add` both work, and a string\n`\"False\"` counts as false, because that is what Entra ID sends.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ScimToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/scim+json": {
              "schema": {
                "$ref": "#/components/schemas/ScimPatchRequest"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScimPatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The patched user, in full.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimUser"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ScimError"
          },
          "401": {
            "$ref": "#/components/responses/ScimError"
          },
          "403": {
            "$ref": "#/components/responses/ScimError"
          },
          "404": {
            "$ref": "#/components/responses/ScimError"
          }
        }
      },
      "delete": {
        "tags": [
          "Extensions",
          "Accounts"
        ],
        "operationId": "scimDeleteUser",
        "summary": "SCIM — deprovision a user",
        "description": "Archives the **membership**. The person's user record is untouched — they may be in other\nworkspaces, and their signatures are evidence. Idempotent.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ScimToken": []
          }
        ],
        "responses": {
          "204": {
            "description": "Deprovisioned."
          },
          "401": {
            "$ref": "#/components/responses/ScimError"
          },
          "403": {
            "$ref": "#/components/responses/ScimError"
          },
          "404": {
            "$ref": "#/components/responses/ScimError"
          }
        }
      }
    },
    "/me/email_change": {
      "get": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "getPendingEmailChange",
        "summary": "Get the pending email change",
        "description": "Always `200`, with all three fields `null` when nothing is pending — a settings page should\nnot have to treat \"no pending change\" as an error. People only.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The pending change, or nulls.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingEmailChange"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/me/email": {
      "post": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "requestEmailChange",
        "summary": "Request an email change",
        "description": "Starts a change of the address this account signs in with. The current password is\nrequired — an open session is not enough to move where the reset links go.\n\n`202` because the address has not changed yet: a link goes to the new address, and both\naddresses are notified. Requesting again supersedes the previous pending change. A\nGoogle-only account must set a password first.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 255
                  },
                  "current_password": {
                    "type": "string"
                  }
                },
                "required": [
                  "email",
                  "current_password"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "A confirmation link is on its way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingEmailChange"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.current_password` reads `That password is not correct.` or `Set a password\nbefore changing the address this account signs in with.`; `errors.email` reads `That is\nalready the address on this account.` or `That address is already used by another\nSignclad account.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "cancelEmailChange",
        "summary": "Cancel a pending email change",
        "description": "Idempotent, and `200` with the all-null body rather than `204` — the caller gets the same\nshape whether or not there was anything to cancel.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "Nothing is pending any more.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingEmailChange"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/me/email/confirm/{token}": {
      "parameters": [
        {
          "name": "token",
          "in": "path",
          "required": true,
          "description": "The signed token from the confirmation email. It is the whole credential.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "confirmEmailChange",
        "summary": "Confirm an email change",
        "description": "Completes the change. Unauthenticated by design — the person clicking the link may be in a\ndifferent browser from the one that asked.\n\nA bad signature, a missing row, and an expired token are one answer, so the link cannot be\nprobed.\n",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "200": {
            "description": "Confirmed. This body carries two fields the other three operations do not: `confirmed`\nand the new `email`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfirmedEmailChange"
                }
              }
            }
          },
          "422": {
            "description": "`errors.token` reads `This confirmation link is no longer valid. Request the change\nagain.`; `errors.email` reads `That address is already used by another Signclad\naccount.` when somebody else took it in the meantime.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/me/notifications": {
      "get": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "getNotificationPreferences",
        "summary": "Get notification preferences",
        "description": "Per person **and** per workspace — the same person can want different mail from two\nworkspaces. Which workspace is decided by the credential, never by the body. Always\ncomplete: defaults are filled in rather than omitted.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The preferences.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationPreferences"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "put": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "updateNotificationPreferences",
        "summary": "Update notification preferences",
        "description": "A **merge**, despite the verb: keys left out keep their value, so a client that knows about\nfewer switches than the server does cannot silently reset the rest. Unknown keys are\nignored.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NotificationPreferences"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The preferences, resolved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationPreferences"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/me/signatures": {
      "get": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "listSavedSignatures",
        "summary": "List saved signatures",
        "description": "A sender's own signatures and initials, default first. Not paginated — nobody has enough of\nthese for it to matter.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The saved signatures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SavedSignature"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "saveSignature",
        "summary": "Save a signature",
        "description": "Three ways in, and the body decides which: a PNG upload in `file`, a base64 PNG data URL in\n`data`, or `text` plus an optional `font` to render one. PNG only, 5 MB.\n\n`method` and `source` are the same field under two names, and each accepts both tenses\n(`draw` and `drawn`). Send either. The first signature of a kind becomes the default\nwhatever the flag says.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SaveSignatureRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/SaveSignatureRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The saved signature.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SavedSignature"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "Under `errors.data` for an image that is missing, not a PNG, too large, or unreadable;\n`errors.text` for a typed signature with nothing to type; `errors.method` and\n`errors.kind` for values outside their enums.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/me/signatures/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "deleteSavedSignature",
        "summary": "Delete a saved signature",
        "description": "Deleting the default promotes the next signature of that kind. Somebody else's id and an id\nthat does not exist answer alike.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/contacts": {
      "get": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "listContacts",
        "summary": "List contacts",
        "description": "The workspace's address book, by name. `q` matches across name, address, company, job title,\nand phone. Readable by any role; writing needs `send_documents`.\n\n`limit` is clamped rather than refused, so an out-of-range value is quietly corrected.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Contact"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "createContact",
        "summary": "Create or update a contact",
        "description": "Create-or-update on the address, matched case-insensitively — so importing the same person\ntwice does not make two rows. It answers `201` either way.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The contact, whether it was created or updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.email` reads `Another contact in this workspace already uses that address.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/contacts/suggest": {
      "get": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "suggestContacts",
        "summary": "Suggest contacts",
        "description": "Type-ahead for a recipient field. At most eight results, and a slimmer object than the list:\nno counts, no timestamps, no pagination.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Up to eight matches.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "maxItems": 8,
                      "items": {
                        "$ref": "#/components/schemas/ContactSuggestion"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/contacts/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "patch": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "updateContact",
        "summary": "Update a contact",
        "description": "Only the keys present change.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated contact.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "`errors.email` reads `Another contact in this workspace already uses that address.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "deleteContact",
        "summary": "Delete a contact",
        "description": "A soft delete: documents they are on keep naming them, because an audit trail that forgets\nwho somebody was is not an audit trail.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/contacts/import": {
      "post": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "importContacts",
        "summary": "Import contacts from a CSV",
        "description": "A CSV with an `email` column; `name`, `company`, `title`, and `phone` are optional, and each\nis matched against a list of spellings, so a file exported from another tool usually just\nworks. Unrecognized columns are reported, not refused.\n\n`200` with a per-row report even when some rows failed — a partial import is the normal\noutcome. It only becomes a `422` when **nothing** could be imported, and then the failures\narrive keyed by row and column under `errors.contacts_csv.rows`.\n\n`update_existing` defaults to **true**: an address already in the book is updated rather\nthan skipped.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "CSV, at most 5 MB and 5000 rows."
                  },
                  "update_existing": {
                    "type": "boolean",
                    "default": true
                  }
                },
                "required": [
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was imported, and what was not.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactImportResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.contacts_csv` when the file cannot be parsed, has no `email` column, or has too\nmany rows; `errors.contacts_csv.rows.<row>.<column>` when every row failed; and\n`errors.base.upload_unreadable` when the upload passed validation and its bytes were\nthen not there. That last one answered a bare `abort(422)` with an empty body until the\nerror-envelope pass.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          }
        }
      }
    },
    "/data_exports": {
      "get": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "listDataExports",
        "summary": "List data exports",
        "description": "Every export this workspace has asked for, newest first. Owner and admin only — an export is\nthe whole workspace in a zip.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of exports.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DataExport"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "requestDataExport",
        "summary": "Request a data export",
        "description": "Queues a zip of the workspace's documents, templates, contacts, and audit log — omit `scope`\nand it means all four. `202`, because the file does not exist yet: poll the export until\n`status` is `ready`, then use `download_url`. Exports are kept seven days.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "scope": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "documents",
                        "templates",
                        "contacts",
                        "audit"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataExport"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/data_exports/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "getDataExport",
        "summary": "Get a data export",
        "description": "Poll this until `status` is `ready`, then follow `download_url`.",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The export.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataExport"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/labels": {
      "get": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "listLabels",
        "summary": "List labels",
        "description": "Every label defined in the workspace, by name. Labels are the workspace's own filing\nsystem: a document carries any number of them, and `PUT /documents/{id}/labels` is what\nputs them there.\n\nThe list carries `id`, `name` and `color` and nothing else; the create and update\nresponses return the whole stored row.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "Every label in the workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LabelRecord"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "createLabel",
        "summary": "Create a label",
        "description": "Names are trimmed and unique within the workspace, so the same label cannot be created\ntwice with different surrounding whitespace. A duplicate is `422`, not a silent reuse.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LabelWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The stored label.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LabelRecord"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/labels/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The label's id.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "patch": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "updateLabel",
        "summary": "Rename or recolor a label",
        "description": "`name` is required on every call — this is a replace of the label's own fields, not a\npartial edit — and the uniqueness check ignores the label being edited, so saving a label\nwithout changing its name is not a conflict.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LabelWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored label.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LabelRecord"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      },
      "delete": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "deleteLabel",
        "summary": "Delete a label",
        "description": "Removes the label and unfiles every document carrying it. The documents themselves are\nuntouched.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/documents/{id}/labels": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "put": {
        "tags": [
          "Extensions",
          "Documents"
        ],
        "operationId": "setDocumentLabels",
        "summary": "Set a document's labels",
        "description": "Replaces the document's whole label set with the ids given, so an empty array clears it.\n`label_ids` must be present even when empty, and every id must already exist in this\nworkspace — this route files a document, it does not create labels.\n\nFiling a document does not touch its sealed content, its revision, or its status, so it\nis allowed at any point in a document's life, including after it has completed.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "documents:write"
            ]
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "label_ids": {
                    "type": "array",
                    "maxItems": 100,
                    "description": "Ids of labels in this workspace. Duplicates are rejected.",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "label_ids"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The document's labels after the change.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LabelRecord"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/embed/documents/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Signer"
        ],
        "operationId": "getEmbeddedEditorDocument",
        "summary": "Read the document an edit session owns",
        "description": "The same document object `GET /documents/{id}` returns, read with an **edit session's**\nbearer token instead of an account credential. The embedded builder runs without a cookie\nand without the host's API key, so it reads through here.\n\nThe session reaches exactly one document: an id that is not the session's subject is a\n`404`, as is a session that has expired, been revoked, or been superseded by a newer\nedit URL.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "SignerSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "The document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Extensions",
          "Signer"
        ],
        "operationId": "updateEmbeddedEditorDocument",
        "summary": "Save the document an edit session owns",
        "description": "The builder's save. Only the keys a builder is allowed to set are accepted — `name`,\n`subject`, `message`, `recipients`, `fields`, `checkbox_groups` and `conditional_rules` —\nand any other key in the body is `422` naming that key, rather than being ignored the way\nan unknown key is everywhere else on the surface. An edit link delegates a builder, not\nthe workspace: it cannot attach arbitrary workspace files, change ownership, archive the\ndocument, or move it between live and test mode.\n\nA workspace that is read-only or suspended refuses the save.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "SignerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmbeddedEditorDocumentSave"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/embed/documents/{id}/send": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Signer"
        ],
        "operationId": "sendEmbeddedEditorDocument",
        "summary": "Send the document an edit session owns",
        "description": "`POST /documents/{id}/send` reached with an edit session's token. The accepted keys are\nnarrower than the account route's: `subject`, `message`, `reminders`, `test_mode`,\n`expires_in`, `redirect_url`, `decline_redirect_url`, `copied_contacts` and\n`attachment_requests`. Anything else is `422` naming the key.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "SignerSession": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmbeddedEditorSendRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The sent document, carrying the transient create status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/embed/document_templates/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/TemplateId"
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Signer"
        ],
        "operationId": "getEmbeddedEditorTemplate",
        "summary": "Read the template an edit session owns",
        "description": "The template equivalent of `GET /embed/documents/{id}`, for a session whose scope is a\ntemplate edit rather than a document edit. The two scopes do not cross: a document\nsession asking for a template is a `404`.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "SignerSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "The template.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Extensions",
          "Signer"
        ],
        "operationId": "saveEmbeddedEditorTemplate",
        "summary": "Save the template an edit session owns",
        "description": "The template builder's save. Accepts `name`, `placeholders`, `fields`, `checkbox_groups`,\n`conditional_rules` and `draft`, and refuses every other key with `422` naming it.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "SignerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmbeddedEditorTemplateSave"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved template.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/embed/preview/{session}/{file}": {
      "parameters": [
        {
          "name": "session",
          "in": "path",
          "required": true,
          "description": "The edit session's id.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "file",
          "in": "path",
          "required": true,
          "description": "The id of a file already attached to the session's document or template.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Signer"
        ],
        "operationId": "previewEmbeddedEditorFile",
        "summary": "Read one file of an edit session",
        "description": "The page bytes the embedded builder renders. The URL is **signed and expiring** and that\nsignature is the whole credential — no header is sent, because an `<img>` or a PDF\nviewer cannot set one.\n\nEverything that is not exactly right is the same `404`: a signature that does not verify\nor has expired, a session that is inactive or superseded, a session whose scope is not an\nedit, and a file that is not attached to that session's own subject.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [],
        "responses": {
          "200": {
            "description": "The file's bytes.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "The signature, the session, or the file did not check out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    },
    "/activity": {
      "post": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "recordActivity",
        "summary": "Record web app activity",
        "description": "Signclad's own web app posts what a person did — clicks, field changes, form submissions,\nnavigations — in batches of up to 50. It exists so that \"what was this account doing when\nit broke\" is answerable from the support surface without reading a session recording, and\nit is not part of anybody's integration.\n\n`event_id` is a UUID the client generates, and a repeat of one already stored is dropped\nrather than duplicated, so a retried batch is safe. `occurred_at` must fall inside the\nlast day and not be in the future by more than a minute.\n\n`expected_user_id` and `expected_workspace_id` guard against a batch queued in one tab\nbeing flushed after the person switched account or workspace: if either disagrees with\nwho is signed in now, the whole batch is `409` and nothing is written.\n\nTelemetry has its own 60-per-minute bucket, so a chatty tab can never spend the\nallowance a person's real actions need.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ActivityBatch"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted. The body is empty.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "description": "The batch was raised by a different user or workspace than the one signed in now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/activity": {
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminListActivity",
        "summary": "Read the activity log",
        "description": "Staff only. The last 30 days of recorded activity across every account — what `POST\n/activity` collected, plus the request log rows, joined to the person who raised them.\n\n50 rows a page, newest first. `next_cursor` is the last row's id; pass it as `before` for\nthe next page, and a `null` means the end. `errors: true` narrows to responses of 400 and\nabove, which is the usual reason to open this screen.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Matches email, name, action, request id, user id, target, or resource ids.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          },
          {
            "name": "user_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "request",
                "click",
                "change",
                "submit",
                "navigation"
              ]
            }
          },
          {
            "name": "errors",
            "in": "query",
            "required": false,
            "description": "Only rows whose response status was 400 or above.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "The `next_cursor` of the previous page.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of activity.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminActivityRow"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/session": {
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminGetSession",
        "summary": "Check the staff session",
        "description": "Staff only. The panel calls this before it renders anything: a `200` means this session\nmay use the staff surface, and the `404` everyone else gets is what the panel turns into\n\"no such page\". It is also the cheapest way to refresh the console's fifteen-minute idle\nclock, which only staff requests advance.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The session may use the staff surface.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "allowed": {
                      "type": "boolean"
                    },
                    "user_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "allowed"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/users": {
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminListUsers",
        "summary": "Search people",
        "description": "Staff only. Every person, newest first, including those whose account is deactivated or\nawaiting deletion — this is the screen for \"they say they cannot sign in\", so a row that\nthe product hides is exactly the row being looked for.\n\nEach row carries the workspaces the person belongs to with their role and the workspace's\nplan, and two decisions the panel would otherwise have to make for itself: `can_delete`\nand `can_impersonate`. Both are `false` for staff and for the caller themselves.\n\nThe search itself is written to the staff audit log.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Matches name or email.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of people.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminUserRow"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "per_page": {
                      "type": "integer"
                    },
                    "last_page": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/users/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The person's user id.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminDeleteUser",
        "summary": "Request a person's deletion",
        "description": "Staff only. Raises the deletion request for a person; the erasure itself runs\nasynchronously. Answers `{\"status\": \"deleted\"}` for an id that is already gone, so a\nretried request is not an error.\n\nStaff accounts cannot be deleted through here, and neither can the caller's own — both\nare `403`. Sending `email` makes the call a confirmed one: it must match the person's\naddress exactly or the request is `422` and nothing happens. `reason` is recorded in the\nstaff audit log alongside the address.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "Typed confirmation. Must equal the person's address."
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The deletion was requested, or the person was already gone.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "deleted"
                      ]
                    }
                  },
                  "required": [
                    "status"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "description": "A staff account, or the caller's own, which cannot be deleted here.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageError"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "The `email` confirmation did not match.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/sent-emails": {
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminListSentEmails",
        "summary": "List sent mail",
        "description": "Staff only. Every message the platform sent in the retention window, newest first, 25 to\na page. This is the screen for \"they say they never got it\": the row's presence answers\nwhether Signclad sent it at all, before anybody goes looking at the provider.\n\nThe list holds headers only — subject, from, to, the mailable class and when it went. The\nrendered body is on the detail route. Responses are `Cache-Control: private, no-store`,\nbecause the list is other people's correspondence.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Matches subject, sender, or any recipient.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of sent mail.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminSentEmailSummary"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "last_page": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/sent-emails/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The sent message's id.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminGetSentEmail",
        "summary": "Read one sent message",
        "description": "Staff only. The whole stored message — including the rendered `html` and `text` bodies as\nthey were sent — plus `html_preview`, a sanitised rendering safe to put in an iframe. A\nmessage older than the retention window is a `404` whether or not the row is still there,\nso the retention promise is enforced on read as well as by the pruning job.\n\nOpening a message is written to the staff audit log, and the response is\n`Cache-Control: private, no-store`.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminSentEmail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/accounts/{account}/usage": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminAccountId"
        }
      ],
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminGetAccountUsage",
        "summary": "Read an account's usage history",
        "description": "Staff only. Documents sent by month for the workspace, and for the account it belongs to,\nso \"how much have they been sending\" stops being a reason to impersonate a customer.\n\n`api_sources` names the `documents.source` values that make a send an API send, which is\nthe same list the usage meter bills from — the panel labels its breakdown from it rather\nthan hard-coding one that could drift.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "parameters": [
          {
            "name": "months",
            "in": "query",
            "required": false,
            "description": "How many months back to include.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The usage history.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUsageHistory"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/accounts/{account}/billing": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminAccountId"
        }
      ],
      "get": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminGetAccountBilling",
        "summary": "Read an account's billing history",
        "description": "Staff only, and read-only on purpose: this is the tab that exists so a billing question\nstops being a reason to impersonate. Plan, seats, period, card, and the recent invoices.\n\nA Stripe outage does not fail the request. `invoices` comes back empty with\n`invoices_error` saying why, so the rest of the screen still renders — the same is true\nfor an environment with no Stripe configured and for an account that has never been\nbilled.\n\nA workspace with no billing account is a `404`.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "responses": {
          "200": {
            "description": "The billing history.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminBillingHistory"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/admin/accounts/{account}/api-pricing": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AdminAccountId"
        }
      ],
      "patch": {
        "tags": [
          "Admin"
        ],
        "operationId": "adminSetAccountApiPricing",
        "summary": "Override an account's API pricing",
        "description": "Staff only. Puts a negotiated per-document API rate on an account, or points it at a\nStripe price, or puts it back on list pricing.\n\n`mode` decides which of the other two fields is required: `rate` needs `rate_usd`,\n`stripe_price` needs `stripe_price_id`, and `default` needs neither and clears whatever\noverride was there.\n\n`reason` is required and is not a formality — an override with no reason is\nindistinguishable from a mistake three months later. It is recorded with the staff member\nwho made it. Answers the account overview, refreshed.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminApiPricingOverride"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The account overview after the change.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminAccountOverview"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/AdminSessionExpired"
          },
          "403": {
            "$ref": "#/components/responses/AdminEmailVerificationRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/security/mfa/email/setup": {
      "post": {
        "tags": [
          "Security"
        ],
        "operationId": "setupEmailMfa",
        "summary": "Start email-code enrolment",
        "description": "Sends a six-digit code to the person's own address and waits for\n`POST /security/mfa/email/confirm`. Nothing is enrolled until that call succeeds.\n\nEmail codes are the weakest of the three factors and are offered as the fallback for\nsomebody who has neither a passkey nor an authenticator. The address has to be verified\nfirst: enrolling an unverified address would turn a typo into a lockout.\n\nA person who has a password must re-enter it here. One who signed up through Google or a\nmagic link has none, and for them the field is not required.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "password": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Required if the person has a password."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A code is on its way.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "sent": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "sent"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.password` reads `Your current password is incorrect.` An unverified address\nanswers `Verify your email address before setting up email codes.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/security/mfa/email/confirm": {
      "post": {
        "tags": [
          "Security"
        ],
        "operationId": "confirmEmailMfa",
        "summary": "Confirm email-code enrolment",
        "description": "Completes the enrolment `POST /security/mfa/email/setup` started. A wrong or expired code\nis `422 errors.code`; the enrolment stays pending and a new code can be requested.\n\nIf this is the person's first factor, a fresh sheet of recovery codes comes back in\n`recovery_codes` — the only time they are ever shown. Somebody who already has unused\nrecovery codes keeps them and the field is `null`.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 6,
                    "maxLength": 6,
                    "description": "The six digits from the message."
                  }
                },
                "required": [
                  "code"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Enrolled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "enrolled": {
                      "type": "boolean"
                    },
                    "recovery_codes": {
                      "type": [
                        "array",
                        "null"
                      ],
                      "description": "A fresh sheet, shown once, or `null` if unused codes remain.",
                      "items": {
                        "type": "string"
                      }
                    },
                    "mfa": {
                      "$ref": "#/components/schemas/MfaStatus"
                    }
                  },
                  "required": [
                    "enrolled"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.code` reads `This code is invalid or expired. Request a new code and try again.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/security/mfa/email": {
      "delete": {
        "tags": [
          "Security"
        ],
        "operationId": "removeEmailMfa",
        "summary": "Turn off email codes",
        "description": "Removes the email factor. A person whose role requires MFA cannot remove their last one:\nwith no passkey and no authenticator left, this answers `422 errors.email` telling them\nto add a passkey first.\n\nA person who has a password must re-enter it.\n",
        "x-signclad-extension": true,
        "security": [
          {
            "ApiKey": []
          },
          {
            "Session": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "password": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Required if the person has a password."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "removed": {
                      "type": "boolean"
                    },
                    "mfa": {
                      "$ref": "#/components/schemas/MfaStatus"
                    }
                  },
                  "required": [
                    "removed"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "`errors.password` reads `Your current password is incorrect.` `errors.email` reads\n`Add a passkey before turning off your last verification method.`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorBody"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/login/mfa/email": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "sendLoginEmailCode",
        "summary": "Send a sign-in code by email",
        "description": "Mails the six-digit code that answers a pending second-factor challenge, for somebody\nwhose factor is email. The challenge lives in the session, so nothing identifies it in\nthe body, and the answer goes back to `POST /login/mfa`.\n\nNo pending challenge, or a person with no email factor enrolled, is `422` — the same\nanswer either way, so the route cannot be used to ask whether an address exists or how it\nis protected.\n",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "200": {
            "description": "A code is on its way.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "sent": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "sent"
                  ]
                }
              }
            }
          },
          "422": {
            "description": "`Sign in again to request a code.`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/inbound/twilio": {
      "post": {
        "tags": [
          "Extensions",
          "Hooks"
        ],
        "operationId": "ingestTwilioEvent",
        "summary": "Twilio inbound message and status callback",
        "description": "Internal. Two things arrive here: a reply from a recipient, and a delivery status for a\nmessage Signclad sent.\n\nA `STOP` (or any opt-out keyword) is recorded on Signclad's side as well as Twilio's.\nTwilio suppresses the number at its end, but a Signclad that did not know would keep\nattempting sends that Twilio rejects, and honouring an opt-out is a carrier requirement\nmeasured per message. The matching opt-in keywords undo it.\n\nA `delivered`, `failed` or `undelivered` status resolves the notification row for that\nSID, which is what eventually makes the recipient's recorded delivery state truthful\ninstead of permanently `sent`. A SID this environment never sent is ignored, not an\nerror.\n\nAuthenticated by `X-Twilio-Signature` — HMAC-SHA1 over the full URL plus every POST\nparameter in key order, keyed by the account auth token — and by nothing else. A\nsignature that does not verify, or a missing one, is `403 invalid_signature` in the\nstandard forbidden envelope, matching the Postmark and SES routes.\n\nThe success answer is an empty `204`, because Twilio reads anything that is not TwiML or\nan empty body as an error in its console.\n",
        "x-signclad-extension": true,
        "x-internal": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "From": {
                    "type": "string",
                    "description": "The sender, for an inbound reply."
                  },
                  "Body": {
                    "type": "string",
                    "description": "The reply text, checked against the opt-out and opt-in keywords."
                  },
                  "MessageSid": {
                    "type": "string",
                    "description": "The provider's message id. Older callbacks send it under the provider's\nlegacy key instead, and that one is read as a fallback. The body is open, so\nevery other field the provider sends arrives intact.\n"
                  },
                  "MessageStatus": {
                    "type": "string",
                    "description": "`delivered`, `failed`, or `undelivered` resolve the message row."
                  },
                  "ErrorCode": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Processed. Empty, which Twilio reads as \"no reply to send\"."
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/support": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "submitSupportRequest",
        "summary": "Send a message to Signclad from inside the app",
        "description": "The signed-in half of `POST /contact`. Same handler and same destination, with one\ndifference that matters: `name` and `email` are taken from the session and whatever the\nbody carried for them is overwritten. A support request therefore always says who\nactually sent it.\n\nNeeds a session; an API key is not a person and cannot raise a support request.\n",
        "x-signclad-extension": true,
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "subject": {
                    "type": "string",
                    "maxLength": 150,
                    "description": "No line breaks — a header-injection guard."
                  },
                  "message": {
                    "type": "string",
                    "maxLength": 10000
                  },
                  "name": {
                    "type": "string",
                    "description": "Ignored. Taken from the session.",
                    "readOnly": true
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Ignored. Taken from the session.",
                    "readOnly": true
                  }
                },
                "required": [
                  "subject",
                  "message"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/mail/unsubscribe": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "post": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "unsubscribeFromMail",
        "summary": "One-click unsubscribe",
        "description": "RFC 8058 one-click unsubscribe, named by the `List-Unsubscribe-Post` header on every\nSignclad message that is not transactional. The caller is Gmail's or Yahoo's\ninfrastructure acting for the reader, so there is no cookie, no session and no CSRF\ntoken: the signature on the URL is the entire credential, and the parameters it was\nsigned with are the parameters it acts on. A leaked link cannot be escalated into\n\"turn everything off\".\n\n`POST` only, because that is what RFC 8058 specifies and because a `GET` unsubscribe\nwould be spent by the first mail scanner to follow it.\n\nIt answers `200` for everything it understands, including a recipient or workspace that\nhas since been deleted and a `kind` it does not recognise. A `4xx` here makes the\nprovider flag the message as having a broken unsubscribe, which is the outcome the\nheader exists to avoid, and \"the document this reminder was about is gone\" is a\nsuccessful unsubscribe from the reader's point of view.\n",
        "x-signclad-extension": true,
        "security": [],
        "parameters": [
          {
            "name": "audience",
            "in": "query",
            "required": false,
            "description": "`recipient` stops a document's reminders; `user` turns off one notification kind.",
            "schema": {
              "type": "string",
              "enum": [
                "recipient",
                "user"
              ]
            }
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "The recipient id, or the user id, depending on `audience`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace",
            "in": "query",
            "required": false,
            "description": "The workspace the preference belongs to. Read when `audience` is `user`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "description": "Which notification preference to turn off. Read when `audience` is `user`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "signature",
            "in": "query",
            "required": true,
            "description": "Laravel's URL signature. Supplied by the link; never constructed by hand.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "expires",
            "in": "query",
            "required": false,
            "description": "The signature's expiry, when the link was issued with one.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Unsubscribed, or nothing needed doing.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "unsubscribed": {
                      "type": "boolean"
                    },
                    "kind": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Which preference was turned off, or `null` when the link named none."
                    }
                  },
                  "required": [
                    "unsubscribed"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The signature did not verify or has expired."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "get": {
        "tags": [
          "Extensions",
          "Account"
        ],
        "operationId": "getApiRoot",
        "summary": "Discover the API",
        "description": "The root of the API origin is a discovery document, not a page. Whoever lands here — a\ndeveloper who pasted the hostname, an agent resolving a base URL — is one hop from the\nreference and from this contract.\n\nThe site origin comes from configuration rather than being hard-coded, so a staging or\nself-hosted deployment points at its own documentation.\n",
        "x-signclad-extension": true,
        "security": [],
        "responses": {
          "200": {
            "description": "Where everything is.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "name": {
                      "type": "string",
                      "examples": [
                        "Signclad API"
                      ]
                    },
                    "api": {
                      "type": "string",
                      "format": "uri",
                      "description": "The versioned base URL."
                    },
                    "health": {
                      "type": "string",
                      "format": "uri"
                    },
                    "documentation": {
                      "type": "string",
                      "format": "uri"
                    },
                    "openapi": {
                      "type": "string",
                      "format": "uri",
                      "description": "This document."
                    }
                  },
                  "required": [
                    "name",
                    "api"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server/{tail}": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "parameters": [
        {
          "$ref": "#/components/parameters/MetadataSuffix"
        }
      ],
      "get": {
        "tags": [
          "OAuth"
        ],
        "operationId": "getAuthorizationServerMetadataSuffixed",
        "summary": "Authorization server metadata, resource-suffixed",
        "description": "The same document as `/.well-known/oauth-authorization-server`, served at the\npath-suffixed form RFC 8414 §3.1 describes for an issuer with a path component. Clients\ndiffer on which of the two they try first, so Signclad answers both identically rather\nthan making the choice matter.\n",
        "security": [],
        "responses": {
          "200": {
            "description": "The metadata document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthorizationServerMetadata"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource/{tail}": {
      "servers": [
        {
          "url": "https://api.signclad.com",
          "description": "Production API root"
        }
      ],
      "parameters": [
        {
          "$ref": "#/components/parameters/MetadataSuffix"
        }
      ],
      "get": {
        "tags": [
          "OAuth"
        ],
        "operationId": "getProtectedResourceMetadataSuffixed",
        "summary": "Protected resource metadata, resource-suffixed",
        "description": "The same document as `/.well-known/oauth-protected-resource`, served at the path-suffixed\nform RFC 9728 describes. The `WWW-Authenticate` header on a `401` points at the bare\npath; both answer identically.\n",
        "security": [],
        "responses": {
          "200": {
            "description": "The metadata document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedResourceMetadata"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "signcladEvent": {
      "post": {
        "tags": [
          "Hooks"
        ],
        "operationId": "receiveSigncladEvent",
        "summary": "Signclad event delivery",
        "description": "Signclad `POST`s this body to every subscribed endpoint. Implement it on your side.\n\n## Headers\n\n| Header | Value |\n|---|---|\n| `X-Signclad-Signature` | `t=<unix>,v1=<hex hmac-sha256(secret, \"<t>.<raw body>\")>`. During a secret rotation overlap the header carries one `v1=` value per valid secret. Tolerance is 5 minutes; compare in constant time. |\n| `X-Signclad-Event` | The event type. |\n| `X-Signclad-Event-Id` | Stable `event.id`. Identical across retries and replays; dedupe on it. |\n| `X-Signclad-Delivery` | Delivery id, fresh per attempt. |\n| `X-Signclad-Attempt` | 1-based attempt number. |\n| `Content-Type` | Always `application/json`. |\n| `User-Agent` | `Signclad/1 (+https://signclad.com/docs/webhooks)`. |\n\nEvery delivery is also verifiable the compatibility way, not only those from endpoints\nregistered through `POST /hooks`: `event.hash` is\n`hex(HMAC-SHA256(key = <endpoint id>, data = \"<type>@<time>\"))`, computed over the header\nline rather than the body.\n\n## Delivery\n\nAt least once, 10 s timeout, any `2xx` counts as delivered. Retries at 1 m, 5 m, 15 m,\n1 h, 3 h, 8 h, 24 h, and 48 h with jitter.\n\n`Retry-After` can only **slow** a retry, never hurry one: the next attempt is the later of\nyour `Retry-After` and the scheduled backoff, clamped to 48 hours. A `410` pauses the\nendpoint at once. Events are dispatched in per-document `sequence` order, but a failing\ndelivery never blocks later events, so receivers that care about order should sort by\n`data.object` state and `event.sequence`. Payloads are snapshots taken when the event\nhappened and never change, on a retry or a replay.\n",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEventEnvelope"
              },
              "examples": {
                "documentCompleted": {
                  "$ref": "#/components/examples/WebhookDocumentCompleted"
                },
                "templateCreated": {
                  "$ref": "#/components/examples/WebhookTemplateCreated"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any `2xx` marks the delivery successful. Respond quickly and process asynchronously;\nthe request times out after 10 seconds.\n"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "A workspace API key from Settings → API. Sandbox keys (`sk_test_`) never bill, and are\nmetered from the same buckets as a live key rather than a tighter one of their own. Emails\nare delivered to recipients with an explicit test notice; documents are watermarked as not\nlegally binding.\n\nA sandbox key is **confined to test-mode data**. Documents, templates and bulk sends are\nfiltered to `test_mode: true` — a live one is a `404`, on reads and writes alike. A file is\nfiltered through whatever owns it: referenced by a live document, template or attachment\nrequest it is a `404`, and one that is not referenced yet is visible only to the kind of\ncredential that uploaded it. A webhook endpoint is filtered by the credential that\nregistered it, so the workspace's production callback is a `404` and cannot be re-pointed\nor deleted by a staging key. It mints and lists only sandbox keys.\n\nEverything else on the surface is refused with `403 sandbox_key_not_allowed`: data exports,\ncontacts, email suppressions, webhook deliveries and replay, members, invitations, billing,\nworkspace settings, SSO, OAuth clients and API applications. What remains reachable is\n`/ping`, `/me`, `/api_keys`, `/documents`, `/document_templates`, `/bulk_sends`, `/files`,\n`/events`, `/template_links`, `/workspaces` and webhook **endpoint** configuration\n(`/hooks`, `/webhook_endpoints`, but not their delivery log, replay, health or\n`rotate_secret`).\n\nDelivery is not confined: an endpoint receives exactly what it subscribed to, whoever\nregistered it, and `include_test_mode` keeps meaning what it always meant. A live key is\nunaffected throughout, and still sees test-mode rows: `test_mode` is a per-object flag, not\na partition of the account.\n"
      },
      "OAuth2": {
        "type": "oauth2",
        "description": "OAuth 2.1 on the API's own origin. PKCE with `S256` is mandatory and there is no implicit\ngrant. A token is scoped to **one workspace**, chosen at consent time.\n\nThe endpoints are advertised at `/.well-known/oauth-authorization-server`; discover them\nrather than hard-coding them. A write scope satisfies the matching read scope. Some routes\nare reachable by **no** scope — anything that mints or revokes a credential — and answer\n`403 insufficient_scope` to any bearer token.\n\nClient credentials is also supported, for server-to-server integrations that act as the\nworkspace rather than as a person; those tokens never carry `offline_access`.\n",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://api.signclad.com/oauth/authorize",
            "tokenUrl": "https://api.signclad.com/oauth/token",
            "refreshUrl": "https://api.signclad.com/oauth/token",
            "scopes": {
              "documents:read": "Read documents, recipients, fields, events, and completed files.",
              "documents:write": "Create, send, update, cancel, and delete documents.",
              "templates:read": "Read templates and placeholders.",
              "templates:write": "Create, update, duplicate, and delete templates.",
              "webhooks:read": "Read webhook endpoints and their delivery logs.",
              "webhooks:write": "Create, update, and delete webhook endpoints.",
              "account:read": "Read the membership, account, and workspace.",
              "offline_access": "Receive a refresh token. Without it, access ends when the token expires."
            }
          },
          "clientCredentials": {
            "tokenUrl": "https://api.signclad.com/oauth/token",
            "scopes": {
              "documents:read": "Read documents, recipients, fields, events, and completed files.",
              "documents:write": "Create, send, update, cancel, and delete documents.",
              "templates:read": "Read templates and placeholders.",
              "templates:write": "Create, update, duplicate, and delete templates.",
              "webhooks:read": "Read webhook endpoints and their delivery logs.",
              "webhooks:write": "Create, update, and delete webhook endpoints.",
              "account:read": "Read the membership, account, and workspace."
            }
          }
        }
      },
      "ScimToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "The SCIM token from `POST /sso/scim/token`. It **is** the tenant: SCIM calls carry no user\nand no role, so this one value identifies the workspace and grants the whole provisioning\nsurface. Rotating it invalidates the old one.\n"
      },
      "RegistrationToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "The `registration_access_token` issued by `POST /oauth/register`. It manages that one\nregistration and nothing else.\n"
      },
      "Session": {
        "type": "apiKey",
        "in": "cookie",
        "name": "signclad_session",
        "description": "The browser session Signclad's own web app holds, set by `POST /login` and the other\nsign-in routes on the API root and sent as a cookie with `X-XSRF-TOKEN` on unsafe methods.\n\nIt is listed because it is what actually reaches most of this surface: everything that\nacts on a **person** rather than on a workspace — MFA enrolment, sessions, passwords,\nprofile and notification settings, member and invitation management, billing — refuses an\nAPI key with `403 api_key_not_a_person`, and no OAuth scope grants it either. Integrations\ndo not use this scheme; it is here so the contract does not imply that an API key reaches\nroutes it cannot.\n"
      },
      "SignerSession": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "A short-lived signer session token (30 minutes, sliding) obtained by opening a signing\nlink or an embedded signing URL and passing the gate. Scoped to one recipient of one\ndocument. Bumping the recipient's or document's `token_version` revokes it. Accepted\nonly by the `Signer` operations.\n"
      }
    },
    "parameters": {
      "DocumentId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The document's id.",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "example": "8f14e45f-ea8f-4b2c-9f0e-1b2c3d4e5f60"
      },
      "TemplateId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The template's id.",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "example": "3f1c9a2e-6b7d-4c8e-9a0b-1c2d3e4f5a6b"
      },
      "BulkSendId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The bulk send's id.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "HookId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The callback's id.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "ApiApplicationId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The API application's id.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "WorkspaceSlug": {
        "name": "workspace_slug",
        "in": "path",
        "required": true,
        "description": "The workspace's slug, as it appears in its SSO URLs.",
        "schema": {
          "type": "string"
        },
        "x-signclad-extension": true
      },
      "AdminAccountId": {
        "name": "account",
        "in": "path",
        "required": true,
        "description": "A **workspace** id, despite the parameter's name — the staff surface is organized by\nworkspace and reports the account behind it.\n",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "x-signclad-extension": true
      },
      "MetadataSuffix": {
        "name": "tail",
        "in": "path",
        "required": true,
        "description": "Whatever follows the well-known prefix. RFC 8414 §3.1 inserts the issuer's path component\nhere; Signclad's issuer has none, so the segment is ignored and the same document is\nserved whatever it holds.\n",
        "schema": {
          "type": "string"
        }
      },
      "AdminDocumentId": {
        "name": "document",
        "in": "path",
        "required": true,
        "description": "The document's id. Staff routes are not scoped to a workspace.",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "x-signclad-extension": true
      },
      "AbuseReportId": {
        "name": "report",
        "in": "path",
        "required": true,
        "description": "The abuse report's id.",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "x-signclad-extension": true
      },
      "DeliveryId": {
        "name": "delivery_id",
        "in": "path",
        "required": true,
        "description": "The delivery's id.",
        "schema": {
          "type": "string"
        },
        "x-signclad-extension": true
      },
      "WebhookEndpointId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The webhook endpoint's id.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "RecipientId": {
        "name": "recipient_id",
        "in": "path",
        "required": true,
        "description": "The recipient's caller-supplied id, as sent in `recipients[].id`.",
        "schema": {
          "type": "string"
        },
        "example": "signer-1"
      },
      "FilePosition": {
        "name": "position",
        "in": "path",
        "required": true,
        "description": "1-based position of the file within the document or template.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Safe retry token, scoped to the **credential** — an API key, an OAuth token or a session\neach keep their own — plus method and path, kept 24 hours. Replaying a completed request\nreturns the stored response, with `Idempotent-Replay: true` on it.\n\nThe same key with a different body is `422`, keyed under the literal field name\n`Idempotency-Key` rather than a base code; a request still in flight is\n`409 idempotency_in_progress`.\n",
        "schema": {
          "type": "string",
          "maxLength": 255
        },
        "example": "9f0e1b2c-3d4e-5f60-8f14-e45fea8f4b2c",
        "x-signclad-extension": true
      },
      "ApiVersion": {
        "name": "Api-Version",
        "in": "header",
        "required": false,
        "description": "Dated API version.\n\n**Not yet implemented.** Nothing reads this header today and no response carries one; every\nrequest is served by `v1`. It is described here because it is the mechanism the contract\nwill use to version itself, and sending it is harmless. Do not branch on a response\nversion that is not there.\n",
        "schema": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "example": "2026-09-09",
        "x-signclad-extension": true
      },
      "SourceHeader": {
        "name": "X-Signclad-Source",
        "in": "header",
        "required": false,
        "description": "Provenance: which client made this request. First-party clients set it — `mcp` from\n`@signclad/mcp`, `zapier` from the Zapier app — alongside a `User-Agent` of\n`signclad-<client>/<version>`.\n\nIt decides what `documents.source` records, and **nothing else**: authorization, billing,\nquota, and the rate-limit bucket all read the credential, never this. A caller that forges\nit gains nothing.\n\nThe list is closed. An unrecognized value is `422` with `errors.source`, not ignored —\nsilently dropping it would leave a first-party client that shipped a typo reporting itself\nas `api` forever, with nothing to notice. Omit the header entirely and provenance falls\nback to the credential.\n",
        "schema": {
          "type": "string",
          "enum": [
            "mcp",
            "zapier"
          ]
        },
        "x-signclad-extension": true
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "Opaque cursor from a previous response's `next_cursor`.",
        "schema": {
          "type": "string"
        },
        "x-signclad-extension": true
      },
      "CursorLimit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Items per page.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 25
        },
        "x-signclad-extension": true
      },
      "FilterStatus": {
        "name": "status",
        "in": "query",
        "required": false,
        "description": "One or more document statuses. Repeat the parameter to pass several.",
        "schema": {
          "type": "array",
          "items": {
            "$ref": "#/components/schemas/DocumentStatus"
          }
        },
        "style": "form",
        "explode": true,
        "x-signclad-extension": true
      },
      "FilterTemplateStatus": {
        "name": "status",
        "in": "query",
        "required": false,
        "description": "One or more template statuses. Accepts a single value or an array — repeat the parameter\nto pass several — and matches case-insensitively, so `draft` and `Draft` both work.\n",
        "schema": {
          "oneOf": [
            {
              "$ref": "#/components/schemas/TemplateStatus"
            },
            {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/TemplateStatus"
              }
            }
          ]
        },
        "style": "form",
        "explode": true,
        "x-signclad-extension": true
      },
      "FilterLabel": {
        "name": "label",
        "in": "query",
        "required": false,
        "description": "Only records carrying this label.",
        "schema": {
          "type": "string"
        },
        "x-signclad-extension": true
      },
      "FilterRecipientEmail": {
        "name": "recipient_email",
        "in": "query",
        "required": false,
        "description": "Only documents with a recipient at this address.",
        "schema": {
          "type": "string",
          "format": "email"
        },
        "x-signclad-extension": true
      },
      "FilterUpdatedSince": {
        "name": "updated_since",
        "in": "query",
        "required": false,
        "description": "Only records changed at or after this time. The reliable way to poll: sort is by\n`updated_at` ascending when this is present.\n",
        "schema": {
          "type": "string",
          "format": "date-time"
        },
        "x-signclad-extension": true
      },
      "FilterMetadata": {
        "name": "metadata",
        "in": "query",
        "required": false,
        "description": "Exact match on stored metadata, expressed as `metadata[key]=value`. Several keys are\ncombined with AND.\n",
        "schema": {
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        },
        "style": "deepObject",
        "explode": true,
        "example": {
          "loan_id": "48213"
        },
        "x-signclad-extension": true
      },
      "FilterQuery": {
        "name": "q",
        "in": "query",
        "required": false,
        "description": "Free-text search over name, subject, recipient names, and recipient emails.",
        "schema": {
          "type": "string"
        },
        "x-signclad-extension": true
      },
      "FilterArchived": {
        "name": "archived",
        "in": "query",
        "required": false,
        "description": "Include archived records. Defaults to excluding them.",
        "schema": {
          "type": "boolean",
          "default": false
        },
        "x-signclad-extension": true
      },
      "FilterTestMode": {
        "name": "test_mode",
        "in": "query",
        "required": false,
        "description": "Restrict to test-mode or live documents. Omitted returns both.",
        "schema": {
          "type": "boolean"
        },
        "x-signclad-extension": true
      },
      "FilterArchivedOnly": {
        "name": "archived_only",
        "in": "query",
        "required": false,
        "description": "Return **only** archived records. Narrower than `archived`, which widens the list to\ninclude them alongside everything else.\n",
        "schema": {
          "type": "boolean",
          "default": false
        },
        "x-signclad-extension": true
      },
      "SortBy": {
        "name": "sort",
        "in": "query",
        "required": false,
        "description": "Which column orders the page. Omitted keeps the default of newest first, which is also\nthe order a cursor walks.\n",
        "schema": {
          "type": "string",
          "enum": [
            "name",
            "recipients",
            "status",
            "updated_at",
            "sealed"
          ]
        },
        "x-signclad-extension": true
      },
      "SortDirection": {
        "name": "direction",
        "in": "query",
        "required": false,
        "description": "Read only when `sort` is given.",
        "schema": {
          "type": "string",
          "enum": [
            "asc",
            "desc"
          ],
          "default": "asc"
        },
        "x-signclad-extension": true
      }
    },
    "headers": {
      "XRateLimitLimit": {
        "description": "Requests allowed in the bucket this request fell into per minute. For an integration:\n120 general, 10 for creates and mutations,\n50 unauthenticated. Present on every response.\n",
        "schema": {
          "type": "integer"
        },
        "example": 120
      },
      "XRateLimitRemaining": {
        "description": "Requests left in this bucket. A failed create still costs one, so a run of `422`s\nexhausts the create bucket.\n",
        "schema": {
          "type": "integer"
        },
        "example": 117
      },
      "XRateLimitReset": {
        "description": "When the bucket refills, as an ISO 8601 timestamp — not a number of seconds.\n",
        "schema": {
          "type": "string",
          "format": "date-time"
        },
        "example": "2026-09-09T20:56:00+00:00"
      },
      "RateLimitLimit": {
        "description": "Additive mirror of `x-ratelimit-limit`, for clients that expect the IETF spelling.\n",
        "schema": {
          "type": "integer"
        },
        "example": 120
      },
      "RateLimitRemaining": {
        "description": "Additive mirror of `x-ratelimit-remaining`.",
        "schema": {
          "type": "integer"
        },
        "example": 117
      },
      "RateLimitReset": {
        "description": "Additive mirror of `x-ratelimit-reset`, expressed as seconds until the bucket refills.\n",
        "schema": {
          "type": "integer"
        },
        "example": 42
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying. Set on every `429`; nothing sets it on a `503`.\n",
        "schema": {
          "type": "integer"
        },
        "example": 30
      },
      "RequestId": {
        "description": "Unique id for this request, quotable in support tickets.",
        "schema": {
          "type": "string"
        },
        "example": "3f9a1c74-8e2b-4d15-9a06-7c4e1b8d20f5"
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid credentials. Unauthenticated requests are rate limited at 50 per\nminute.\n\nThe response carries `WWW-Authenticate: Bearer resource_metadata=\"…\"` per RFC 9728 §5.1,\npointing at `/.well-known/oauth-protected-resource`. That is how a client which has never\nseen this server before — an MCP client, say — finds the authorization server without being\nconfigured with it. An API key caller ignores it.\n",
        "headers": {
          "WWW-Authenticate": {
            "description": "`Bearer resource_metadata=\"https://api.signclad.com/.well-known/oauth-protected-resource\"`.",
            "schema": {
              "type": "string"
            }
          },
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/UnauthorizedError"
            },
            "examples": {
              "unauthorized": {
                "summary": "A bad API key",
                "value": {
                  "message": "Missing or invalid authorization key",
                  "meta": {
                    "error": "api_key_unauthorized_error",
                    "message": "Not valid authorization token",
                    "messages": [
                      "Not valid authorization token"
                    ]
                  }
                }
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "Authenticated, but not allowed to do this: an insufficient role, a credential type the\nroute refuses, an unenrolled second factor, or a signer acting out of turn. `meta.error`\nalways says which.\n\nA resource in another workspace is a `404` rather than a `403`, so an id is never confirmed\nby a permission error.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ForbiddenError"
            },
            "examples": {
              "apiKeyNotAPerson": {
                "summary": "A route that needs a person, called with an API key",
                "value": {
                  "message": "Forbidden",
                  "meta": {
                    "error": "api_key_not_a_person",
                    "message": "This endpoint needs a signed-in person, not an API key.",
                    "messages": [
                      "This endpoint needs a signed-in person, not an API key."
                    ]
                  }
                }
              },
              "insufficientRole": {
                "summary": "A role that lacks the capability the route needs",
                "value": {
                  "message": "Forbidden",
                  "meta": {
                    "error": "insufficient_permissions",
                    "message": "Your role does not allow this action.",
                    "messages": [
                      "Your role does not allow this action."
                    ],
                    "required_capability": "manage_settings",
                    "role": "member"
                  }
                }
              }
            }
          }
        }
      },
      "PlanRestricted": {
        "description": "The request was well formed; the account may not make it right now. All three of these\nuse the recorded code-keyed `422` envelope under `errors.base`, so a client has one branch\nto write:\n\n- `not_available_on_plan` — the plan does not include the feature, or the feature ships in\n  a later phase. `meta.feature` names it.\n- `account_read_only` — billing is behind; reads still work.\n- `template_read_only` — the workspace has more templates than the plan includes, so the\n  ones over the cap are read-only rather than deleted. `meta.template_limit` is the cap.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationErrorBody"
            },
            "examples": {
              "notOnPlan": {
                "value": {
                  "errors": {
                    "base": {
                      "not_available_on_plan": "This feature is not available on your plan."
                    }
                  },
                  "meta": {
                    "feature": "bulk_send"
                  },
                  "request_id": "3f9a1c74-8e2b-4d15-9a06-7c4e1b8d20f5"
                }
              },
              "readOnly": {
                "value": {
                  "errors": {
                    "base": {
                      "account_read_only": "This account is read-only until its billing is up to date."
                    }
                  }
                }
              },
              "templateReadOnly": {
                "value": {
                  "errors": {
                    "base": {
                      "template_read_only": "This template is read-only: your plan includes fewer templates than this workspace has."
                    }
                  },
                  "meta": {
                    "template_limit": 5
                  }
                }
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "No such record for this workspace. Signclad never answers `404` for a record that\nexists: a `404` is authoritative.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/NotFoundError"
            },
            "examples": {
              "notFound": {
                "value": {
                  "message": "Not found",
                  "meta": {
                    "error": "record_not_found",
                    "message": "Couldn't find the document requested",
                    "messages": [
                      "Couldn't find the document requested"
                    ]
                  }
                }
              }
            }
          }
        }
      },
      "Conflict": {
        "description": "The request is well formed; the resource is not in a state that allows it. **Every** `409`\ncarries `meta.error`, `meta.message`, `meta.messages[]` and `request_id`, whichever domain\nraised it, so a client can branch on one field rather than parsing prose.\n\n`message` here is the human-readable detail, not a fixed word — unlike `403`, where\n`message` is always `Forbidden` and the detail lives in `meta.message`.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ConflictError"
            },
            "examples": {
              "notActive": {
                "summary": "The document has left the state this operation needs",
                "value": {
                  "message": "Document is no longer active",
                  "meta": {
                    "error": "document_not_active",
                    "message": "Document is no longer active",
                    "messages": [
                      "Document is no longer active"
                    ],
                    "state": "completed"
                  },
                  "request_id": "3f9a1c74-8e2b-4d15-9a06-7c4e1b8d20f5"
                }
              },
              "selfRevoke": {
                "summary": "An API key cannot revoke itself",
                "value": {
                  "message": "An API key cannot revoke itself. Use another credential.",
                  "meta": {
                    "error": "self_revoke",
                    "message": "An API key cannot revoke itself. Use another credential.",
                    "messages": [
                      "An API key cannot revoke itself. Use another credential."
                    ]
                  },
                  "request_id": "3f9a1c74-8e2b-4d15-9a06-7c4e1b8d20f5"
                }
              },
              "alreadyReviewed": {
                "summary": "Somebody already decided this abuse report",
                "value": {
                  "message": "This report was already banned by ops@example.com.",
                  "meta": {
                    "error": "abuse_report_already_reviewed",
                    "message": "This report was already banned by ops@example.com.",
                    "messages": [
                      "This report was already banned by ops@example.com."
                    ]
                  },
                  "request_id": "3f9a1c74-8e2b-4d15-9a06-7c4e1b8d20f5"
                }
              }
            }
          }
        }
      },
      "OAuthError": {
        "description": "The OAuth error envelope: `error` plus a human-readable `error_description`. Always\n`Cache-Control: no-store`.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OAuthErrorBody"
            },
            "examples": {
              "badGrant": {
                "value": {
                  "error": "invalid_grant",
                  "error_description": "This authorization code has already been used. Codes are single use."
                }
              }
            }
          }
        }
      },
      "OAuthUnauthorizedClient": {
        "description": "Client authentication failed.",
        "headers": {
          "WWW-Authenticate": {
            "description": "`Basic realm=\"signclad\", error=\"invalid_client\"`.",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OAuthErrorBody"
            },
            "examples": {
              "invalidClient": {
                "value": {
                  "error": "invalid_client",
                  "error_description": "Client authentication failed."
                }
              }
            }
          }
        }
      },
      "InsufficientScope": {
        "description": "The OAuth token does not carry a scope that grants this route — and some routes, such as\nanything that mints or revokes credentials, are reachable by **no** scope at all.\n",
        "headers": {
          "WWW-Authenticate": {
            "description": "`Bearer realm=\"signclad\", error=\"insufficient_scope\"`, with a description.",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ForbiddenError"
            },
            "examples": {
              "noScope": {
                "value": {
                  "message": "Forbidden",
                  "meta": {
                    "error": "insufficient_scope",
                    "message": "This endpoint cannot be called with an OAuth access token.",
                    "messages": [
                      "This endpoint cannot be called with an OAuth access token."
                    ],
                    "required_scope": null
                  }
                }
              }
            }
          }
        }
      },
      "BillingUnavailable": {
        "description": "The billing provider could not be reached, or answered in a way Signclad could not use.\n`meta.error` is `billing_unavailable`. Nothing was charged and nothing changed; retry.\n\nThis is the one `503` the API raises deliberately. It carries no `Retry-After`.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/MessageError"
            },
            "examples": {
              "unavailable": {
                "value": {
                  "message": "Billing is temporarily unavailable. Try again in a moment.",
                  "meta": {
                    "error": "billing_unavailable",
                    "message": "Billing is temporarily unavailable. Try again in a moment.",
                    "messages": [
                      "Billing is temporarily unavailable. Try again in a moment."
                    ]
                  },
                  "request_id": "3f9a1c74-8e2b-4d15-9a06-7c4e1b8d20f5"
                }
              }
            }
          }
        }
      },
      "AdminEmailVerificationRequired": {
        "description": "Staff, but with an unverified email address. This is the **only** non-`404` refusal on the\nstaff surface: everybody else — a stranger, a customer, an API key, an impersonating session\n— gets `404`, so the existence of the panel is never confirmed. Somebody who has already\nproved they are staff is told why nothing works instead, because hiding the surface from\nthem buys nothing.\n\nA verified address is the whole requirement. The panel does not demand a second factor on\ntop of it.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/MessageError"
            },
            "examples": {
              "verifyEmail": {
                "value": {
                  "message": "Verify your email address before using the admin panel.",
                  "meta": {
                    "error": "admin_email_verification_required",
                    "message": "Verify your email address before using the admin panel.",
                    "messages": [
                      "Verify your email address before using the admin panel."
                    ]
                  }
                }
              }
            }
          }
        }
      },
      "AdminSessionExpired": {
        "description": "The staff console has its own idle timeout — fifteen minutes, shorter than any customer\nsession, because this is the surface where one credential reaches every customer's data.\nPassing it ends the **session**, not just the surface: the row is revoked, a security event\nis recorded, and getting back in means a fresh sign-in.\n\nOnly staff requests advance the clock, so time spent in the product does not hold the\nconsole open.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/UnauthorizedError"
            }
          }
        }
      },
      "ScimError": {
        "description": "The one exception: a `403` for a surface this workspace may not use carries no `scimType`\nat all, only `status` and `detail`. Read `scimType` as optional.\n\n\nThe SCIM error envelope. `scimType` narrows the reason: `uniqueness` for an address already\nprovisioned, `invalidFilter` for a filter other than `userName eq`, `invalidValue` for a\nbody or patch this surface will not take.\n",
        "content": {
          "application/scim+json": {
            "schema": {
              "$ref": "#/components/schemas/ScimErrorBody"
            },
            "examples": {
              "duplicate": {
                "value": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:Error"
                  ],
                  "status": "409",
                  "scimType": "uniqueness",
                  "detail": "A user with userName ada@example.com already exists."
                }
              },
              "notPatchable": {
                "value": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:Error"
                  ],
                  "status": "400",
                  "scimType": "invalidValue",
                  "detail": "Only the `active` attribute can be patched; got `displayName`."
                }
              }
            }
          }
        }
      },
      "ProviderNotConfigured": {
        "description": "The capability is available on the plan but the environment has no provider behind it — an\nunknown driver name, or missing credentials. `meta.capability` says which.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/MessageError"
            },
            "examples": {
              "noProvider": {
                "value": {
                  "message": "Identity verification is not configured in this environment.",
                  "meta": {
                    "error": "provider_not_configured",
                    "message": "Identity verification is not configured in this environment.",
                    "messages": [
                      "Identity verification is not configured in this environment."
                    ],
                    "capability": "id_verification"
                  }
                }
              }
            }
          }
        }
      },
      "Gone": {
        "description": "The link cannot be exchanged: it was revoked, superseded, already used, or has expired.\n`meta.error` is `link_not_found`, `link_revoked`, `link_expired`, `link_already_used`, or\n`link_superseded`, and the signer app shows the matching reason page.\n\nAn unknown token and a revoked one are answered identically. Anything else would be an\noracle: a probe could tell a real recipient id from a fabricated one.\n\nOnly `POST /signer/exchange` answers this. Once a session exists it is the session that\nis checked, and a revoked or superseded one is a `401`.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/SignerError"
            },
            "examples": {
              "revoked": {
                "value": {
                  "message": "This signing link is no longer valid",
                  "meta": {
                    "error": "link_revoked",
                    "message": "This signing link is no longer valid",
                    "messages": [
                      "This signing link is no longer valid"
                    ]
                  },
                  "request_id": "3f9a1c74-8e2b-4d15-9a06-7c4e1b8d20f5"
                }
              }
            }
          }
        }
      },
      "SignerForbidden": {
        "description": "The session is valid; this action is not permitted. `meta.error` is one of\n`consent_required`, `decline_not_allowed`, `reassign_not_allowed`,\n`download_not_available` (with `meta.state`), `scope_not_allowed` (with `meta.scope`),\nor a gate code.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/SignerError"
            },
            "examples": {
              "consentRequired": {
                "value": {
                  "message": "Electronic signature consent is required first",
                  "meta": {
                    "error": "consent_required",
                    "message": "Electronic signature consent is required first",
                    "messages": [
                      "Electronic signature consent is required first"
                    ]
                  },
                  "request_id": "3f9a1c74-8e2b-4d15-9a06-7c4e1b8d20f5"
                }
              }
            }
          }
        }
      },
      "SignerConflict": {
        "description": "The document no longer accepts signer activity, or the revision moved under the signer.\n`meta.error` is `document_expired`, `document_canceled`, `document_declined`,\n`document_not_sent`, `document_finalizing`, `document_not_active`, or\n`revision_mismatch`; `meta.state` carries the internal state.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/SignerError"
            },
            "examples": {
              "expired": {
                "value": {
                  "message": "This document has expired",
                  "meta": {
                    "error": "document_expired",
                    "message": "This document has expired",
                    "messages": [
                      "This document has expired"
                    ],
                    "state": "expired"
                  },
                  "request_id": "3f9a1c74-8e2b-4d15-9a06-7c4e1b8d20f5"
                }
              }
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "Validation failed. `errors` maps a field name to a message string, or to an object keyed\nby an error code or by a per-item key. Values are never arrays.\n",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationErrorBody"
            },
            "examples": {
              "plainMessage": {
                "summary": "A field-level message",
                "value": {
                  "errors": {
                    "files": "At least one file should be present."
                  }
                }
              },
              "codeKeyed": {
                "summary": "Keyed by an error code",
                "value": {
                  "errors": {
                    "recipients": {
                      "duplicated_emails": "These emails are duplicated: fixture-dup@example.com."
                    }
                  }
                }
              },
              "severalProblems": {
                "summary": "Two problems at once, one of them keyed per item",
                "value": {
                  "errors": {
                    "template_id": {
                      "not_in_account": "There's no template with id 00000000-0000-0000-0000-000000000000 in your account."
                    },
                    "recipients": {
                      "recipient_1": {
                        "unexisting_placeholder_name": "This placeholder_name is not present in the template(s): signer."
                      }
                    }
                  },
                  "type": "https://developers.signclad.com/errors/validation_failed",
                  "request_id": "3f9a1c74-8e2b-4d15-9a06-7c4e1b8d20f5"
                }
              }
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limited. The message contains the literal `429 Too Many Requests` for compatibility\nwith clients that match on it.\n\nTwo different `meta.error` codes land here: `rate_limit_exceeded` when a bucket is empty,\nand `account_locked_out` after repeated failed sign-in attempts for one account.\n`Retry-After` is set on both; only the lockout also carries `meta.retry_after`.\n",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/XRateLimitLimit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/XRateLimitRemaining"
          },
          "x-ratelimit-reset": {
            "$ref": "#/components/headers/XRateLimitReset"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/MessageError"
            },
            "examples": {
              "limited": {
                "summary": "The bucket is empty",
                "value": {
                  "message": "Too many requests. 429 Too Many Requests",
                  "meta": {
                    "error": "rate_limit_exceeded",
                    "message": "Too many requests. 429 Too Many Requests",
                    "messages": [
                      "Too many requests. 429 Too Many Requests"
                    ]
                  }
                }
              },
              "lockedOut": {
                "summary": "Too many failed sign-in attempts for this account",
                "value": {
                  "message": "Too many failed attempts. Try again in 15 minute(s).",
                  "meta": {
                    "error": "account_locked_out",
                    "message": "Too many failed attempts. Try again in 15 minute(s).",
                    "messages": [
                      "Too many failed attempts. Try again in 15 minute(s)."
                    ],
                    "retry_after": 900
                  }
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "DocumentStatus": {
        "type": "string",
        "title": "Document status",
        "description": "The document's wire status. Casing is exactly as spelled here and is pinned by recorded\nfixtures; clients that compare case-insensitively are also safe.\n\n`Created` is transient: it is what a create answers, whatever `draft` was. `Draft` is\nwhat a send answers. The settled status appears on the next `GET`.\n\n`Bounced` means a recipient's delivery bounced and that recipient has not started. The\ndocument is still active, the other recipients keep signing, the bounced recipients stay\n`sent` with `bounced: true`, and the document-level `error_message` names the last bad\naddress. `Pending` is also what a document sealing after its last signature reads as, so\na sender never sees a half-completed document.\n",
        "enum": [
          "Draft",
          "Created",
          "Sending",
          "Sent",
          "Pending",
          "Viewed",
          "Completed",
          "Manually completed",
          "Declined",
          "Canceled",
          "Bounced",
          "Blocked",
          "Error",
          "Expired"
        ]
      },
      "RecipientStatus": {
        "type": "string",
        "title": "Recipient status",
        "description": "Lowercase, and a different vocabulary from the document's status.\n`created → draft → sent → viewed → completed`, or one of the terminal states. A\nrecipient who has finished signing reads **`completed`**, not `signed`.\n\nA bounced recipient stays `sent` and carries `bounced: true` with `bounced_details`;\nit is the document that reads `Bounced`. `reassigned` is additive and only appears on a\nrecipient who handed their turn to somebody else.\n",
        "enum": [
          "created",
          "draft",
          "sent",
          "viewed",
          "completed",
          "declined",
          "bounced",
          "reassigned"
        ]
      },
      "TemplateStatus": {
        "type": "string",
        "title": "Template status",
        "description": "`Created` is the transient status a create answers, whatever `draft` was. On the next\n`GET` the template reads `Draft` while it is still being built and `Available` once it\nis finished; `PUT {draft: false}` is what moves it. Only an `Available`, unarchived\ntemplate can spawn documents.\n",
        "enum": [
          "Created",
          "Draft",
          "Available"
        ]
      },
      "FieldType": {
        "type": "string",
        "title": "Field type",
        "description": "`autofill_*` fields are filled from the recipient and their contact record and are never\neditable by the signer.\n",
        "enum": [
          "initials",
          "signature",
          "checkbox",
          "date",
          "select",
          "text",
          "dropdown",
          "autofill_company",
          "autofill_email",
          "autofill_first_name",
          "autofill_last_name",
          "autofill_name",
          "autofill_phone",
          "autofill_title",
          "autofill_date_signed"
        ]
      },
      "FieldValidation": {
        "type": "string",
        "title": "Field validation",
        "description": "Text validation applied as the signer types. `custom` uses `validation_regex`.\n\nThe typed validations beyond `no_text_validation` are a **Business** feature. Asking for\none on a plan that does not include it is `422 not_available_on_plan` at create time, not\na silent downgrade.\n",
        "enum": [
          "no_text_validation",
          "numbers",
          "letters",
          "email_address",
          "us_phone_number",
          "us_zip_code",
          "us_ssn",
          "us_age",
          "alphanumeric",
          "us_bank_routing_number",
          "us_bank_account_number",
          "custom"
        ]
      },
      "DateFormat": {
        "type": "string",
        "title": "Date format",
        "description": "How a date field renders once filled.",
        "enum": [
          "MM/DD/YYYY",
          "DD/MM/YYYY",
          "YYYY/MM/DD",
          "Month DD YYYY",
          "MM/DD/YYYY hh:mm:ss a"
        ]
      },
      "Language": {
        "type": "string",
        "title": "Language",
        "description": "Language for the signing experience and its emails. Arabic renders right to left.",
        "enum": [
          "en",
          "fr",
          "es",
          "de",
          "pl",
          "pt",
          "da",
          "nl",
          "it",
          "ru",
          "sv",
          "ar",
          "el",
          "tr",
          "sk"
        ],
        "default": "en"
      },
      "DeliveryMethod": {
        "type": "string",
        "title": "Delivery method",
        "description": "Signing links are delivered by email.",
        "enum": [
          "email"
        ],
        "default": "email"
      },
      "EventType": {
        "type": "string",
        "title": "Event type",
        "description": "Every event Signclad emits. `document_viewed` fires on each view; `document_in_progress`\nfires once, the first time any value is saved; `document_signed` fires per recipient.\n`template_deleted`, `bulk_send_completed`, `template_link_used` and `ping` are additive —\na compatibility client will not have seen them. `ping` is only ever produced by an\nexplicit test.\n\nA deployment may have events switched off, in which case they are absent from\n`GET /events` and cannot be subscribed to. Read the catalog rather than this list when you\nneed to know what a particular deployment will send.\n",
        "enum": [
          "document_created",
          "document_sent",
          "document_viewed",
          "document_in_progress",
          "document_recipients_updated",
          "document_signed",
          "document_completed",
          "document_expired",
          "document_canceled",
          "document_declined",
          "document_bounced",
          "document_error",
          "template_created",
          "template_updated",
          "template_error",
          "template_deleted",
          "bulk_send_completed",
          "template_link_used",
          "ping"
        ]
      },
      "Metadata": {
        "type": "object",
        "title": "Metadata",
        "description": "Up to 50 key/value pairs stored with the record and returned unchanged. Keys are under\n40 characters, values under 500, and **values must be strings** — encode anything else\nyourself.\n",
        "additionalProperties": {
          "type": "string",
          "maxLength": 500
        },
        "maxProperties": 50,
        "example": {
          "loan_id": "48213",
          "source": "portal"
        }
      },
      "Warning": {
        "type": "object",
        "title": "Warning",
        "description": "Something Signclad accepted but changed or ignored, where the compatibility target would\nhave rejected the whole request. Warnings never make a request fail.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable machine-readable code. The set Signclad emits today is\n`lock_sign_date_value_ignored`, `field_skipped`, `template_field_skipped`,\n`field_api_id_collision`, `pre_signed_content_superseded` and `no_text_tags_found`.\nTreat an unrecognised code as informational rather than failing on it.\n",
            "examples": [
              "lock_sign_date_value_ignored",
              "template_field_skipped",
              "field_api_id_collision"
            ]
          },
          "message": {
            "type": "string",
            "description": "Plain-language explanation."
          },
          "pointer": {
            "type": "string",
            "description": "JSON pointer into the request body that caused it, when there is one.",
            "examples": [
              "/template_fields/3"
            ]
          }
        },
        "required": [
          "code",
          "message"
        ]
      },
      "MessageError": {
        "type": "object",
        "title": "Message error",
        "description": "The bare error envelope used by 400, 401, 403, and 429.",
        "additionalProperties": true,
        "properties": {
          "message": {
            "type": "string",
            "description": "Human-readable message."
          },
          "request_id": {
            "type": "string",
            "description": "The failing request's id.",
            "x-signclad-extension": true
          }
        },
        "required": [
          "message"
        ]
      },
      "NotFoundError": {
        "type": "object",
        "title": "Not found",
        "description": "The 404 envelope. `meta.error` is always `record_not_found`, including for a\n`completed_pdf` that is not ready yet — the two bodies are byte-identical.\n\n`meta.message` names the kind of thing that was not found — `Couldn't find the passkey\nrequested`, `... the workspace requested`, `... the bulk send requested` — for the\nextension resources. Documents, templates, and recipients keep the recorded wording,\n`Couldn't find the document requested`, because a compatibility client matches on it.\n",
        "additionalProperties": true,
        "properties": {
          "message": {
            "type": "string",
            "const": "Not found"
          },
          "meta": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "error": {
                "type": "string",
                "const": "record_not_found"
              },
              "message": {
                "type": "string",
                "description": "Names the resource kind: `document`, `template`, `recipient`, `passkey`, `session`,\n`API key`, `API application`, `webhook endpoint`, `webhook delivery`, `bulk send`,\n`OAuth client`, `connected application`, `member`, `invitation`, `workspace`,\n`report`, `refund`, `impersonation`.\n",
                "examples": [
                  "Couldn't find the workspace requested"
                ]
              },
              "messages": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "required": [
              "error"
            ]
          },
          "request_id": {
            "type": "string",
            "x-signclad-extension": true
          }
        },
        "required": [
          "message",
          "meta"
        ]
      },
      "UnauthorizedError": {
        "type": "object",
        "title": "Unauthorized",
        "description": "The 401 envelope. It is JSON, not a bare string, and `meta` repeats the reason in both\nsingular and plural form.\n",
        "additionalProperties": true,
        "properties": {
          "message": {
            "type": "string",
            "examples": [
              "Missing or invalid authorization key"
            ]
          },
          "meta": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "error": {
                "type": "string",
                "examples": [
                  "api_key_unauthorized_error"
                ]
              },
              "message": {
                "type": "string",
                "examples": [
                  "Not valid authorization token"
                ]
              },
              "messages": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "required": [
              "error"
            ]
          },
          "request_id": {
            "type": "string",
            "x-signclad-extension": true
          }
        },
        "required": [
          "message",
          "meta"
        ]
      },
      "ForbiddenError": {
        "type": "object",
        "title": "Forbidden",
        "description": "The 403 envelope. `message` is always the literal `Forbidden`, matching the compatibility\nbody; the detail and the code live in `meta`, which is **always present** — every refusal\nsays why, in a field a client can switch on.\n",
        "additionalProperties": true,
        "properties": {
          "message": {
            "type": "string",
            "const": "Forbidden"
          },
          "meta": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "error": {
                "type": "string",
                "description": "`insufficient_permissions` — the role lacks the capability, named in\n`required_capability`. `api_key_not_a_person` — the route needs a human and a key\nis not one. `registration_closed`. `mfa_enrollment_required` — enrol before\ncontinuing; `meta.mfa` carries the policy. `admin_email_verification_required` —\nstaff whose own address is not verified yet, the one non-`404` refusal the staff\nsurface makes. `insufficient_scope` — the OAuth token does not carry the scope, or\nno scope grants this route at all. `workspace_suspended` — the workspace is\nsuspended and may not create new work; reads and downloads still answer.\n`email_verification_required` — the person has not proved control of their address\nyet, which gates the settings surfaces and not `/me`.\n`sandbox_key_not_allowed` — the credential is an `sk_test_` key and the route is\nnot one of the families a sandbox key may reach; sandbox keys work with test-mode\nresources and nothing else, so documents, templates, bulk sends, files, API keys\nand webhook endpoint configuration answer normally and the rest of the surface\nanswers this. `invalid_signature` and `unknown_topic` are the inbound provider\nwebhooks (`/inbound/postmark`, `/inbound/ses`, `/inbound/twilio`). The signer\nsurface adds its own codes.\n\nThere is no `staff_only`: `/admin/*` answers `404` to everyone who is not staff, so\nthat the surface does not confirm its own existence.\n",
                "examples": [
                  "insufficient_permissions",
                  "api_key_not_a_person",
                  "registration_closed",
                  "mfa_enrollment_required",
                  "admin_email_verification_required",
                  "insufficient_scope",
                  "workspace_suspended",
                  "sandbox_key_not_allowed",
                  "invalid_signature"
                ]
              },
              "message": {
                "type": "string",
                "description": "The detail. Defaults to `You are not allowed to do this.`"
              },
              "messages": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "required_capability": {
                "type": "string",
                "enum": [
                  "view_documents",
                  "send_documents",
                  "manage_templates",
                  "manage_members",
                  "manage_settings",
                  "manage_billing"
                ]
              },
              "role": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "owner",
                  "admin",
                  "member",
                  "viewer",
                  null
                ]
              },
              "mfa": {
                "$ref": "#/components/schemas/MfaPolicy"
              }
            },
            "required": [
              "error",
              "message"
            ]
          },
          "request_id": {
            "type": "string",
            "x-signclad-extension": true
          }
        },
        "required": [
          "message",
          "meta"
        ]
      },
      "ConflictError": {
        "type": "object",
        "title": "Conflict",
        "description": "Every `409`, in one envelope. `message` is the detail; `meta.error` is the code to branch\non; `meta.message` and `meta.messages[]` repeat the detail so a client that reads refusals\nout of `meta` has one code path for `403`, `404` and `409` alike; and `meta` carries\nwhatever else is worth knowing — the document's state, the revision that was expected.\n`request_id` is always present.\n\nThree families used to answer this status with three different bodies: the signer surface\ncarried `meta.message`/`meta.messages`, the documents and templates state conflicts carried\nonly the code and their state, and a controller-level conflict carried neither those nor a\n`request_id`. They are one shape now. The state keys are unchanged and remain additive.\n",
        "additionalProperties": true,
        "properties": {
          "message": {
            "type": "string"
          },
          "meta": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "error": {
                "type": "string",
                "description": "`document_not_active`, `document_finalizing`, and `revision_mismatch` come from the\ndocuments and templates domains and carry their own state alongside.\n`idempotency_in_progress` means the same key is still in flight. `self_revoke`,\n`no_account`, `already_signed_in`, `abuse_report_already_reviewed`,\n`subscription_exists`, `recipient_not_a_signer` and `recipient_not_signable` are\nthe controller-level conflicts. `conflict` is the fallback.\n\nThe list is what the server emits today, not a closed set for all time: treat an\nunrecognised code as the generic conflict rather than failing on it.\n",
                "enum": [
                  "document_not_active",
                  "document_finalizing",
                  "document_expired",
                  "document_canceled",
                  "document_declined",
                  "document_not_sent",
                  "revision_mismatch",
                  "idempotency_in_progress",
                  "self_revoke",
                  "no_account",
                  "already_signed_in",
                  "abuse_report_already_reviewed",
                  "recipient_not_a_signer",
                  "recipient_not_signable",
                  "region_change_in_progress",
                  "subscription_exists",
                  "conflict"
                ]
              },
              "message": {
                "type": "string",
                "description": "The same detail as the top-level `message`."
              },
              "messages": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "state": {
                "type": "string",
                "description": "The resource's current state, when it has one."
              },
              "expected_revision": {
                "type": "integer"
              },
              "current_revision": {
                "type": "integer"
              }
            },
            "required": [
              "error",
              "message",
              "messages"
            ]
          },
          "request_id": {
            "type": "string",
            "x-signclad-extension": true
          }
        },
        "required": [
          "message",
          "meta"
        ]
      },
      "ValidationErrorValue": {
        "title": "Validation error value",
        "description": "Either the message itself, or an object one level further down, keyed by an error code\n(`duplicated_emails`, `not_in_account`, `invalid_date_format`) or by a per-item key (a\nrecipient's `recipient_1`, a field's `api_id`). Nesting is arbitrarily deep; the leaves\nare always strings, never arrays.\n",
        "oneOf": [
          {
            "type": "string"
          },
          {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/ValidationErrorValue"
            }
          }
        ]
      },
      "ValidationErrorBody": {
        "type": "object",
        "title": "Validation error",
        "description": "The 422 envelope. `errors` maps a request field to a message string or to a nested\ncode-keyed object — **never to an array**. `base` holds errors that belong to no single\nfield. Several unrelated problems are reported in one body rather than one at a time.\n\nThere is no top-level `message` on a 422, and only the first message per field survives.\nA dotted field name nests: `credential.id` arrives as `errors.credential.id`.\n\nA refusal that belongs to the request rather than to a field is keyed under `base` by its\ncode: `not_available_on_plan`, `account_read_only`, `template_read_only`,\n`document_limit_reached`, `region_change_in_progress`, `file_rejected` (a file whose type\nor size the intake will not take), `upload_unreadable` (a file that passed validation and\nthen could not be read), and `unprocessable_entity` for anything that names no better\ncode. Every 422 uses this envelope — none falls back to the framework's own\n`{\"message\": \"\"}`.\n\nTwo keys are not field names. `base` is one. The other is `Idempotency-Key`: a reused key\nwith a different body reports under that literal header name rather than under `base`.\n",
        "additionalProperties": true,
        "properties": {
          "errors": {
            "type": "object",
            "description": "Field name to a message, or to a nested object of them.",
            "additionalProperties": {
              "$ref": "#/components/schemas/ValidationErrorValue"
            }
          },
          "request_id": {
            "type": "string",
            "description": "The failing request's id.",
            "x-signclad-extension": true
          }
        },
        "required": [
          "errors"
        ]
      },
      "CursorPage": {
        "type": "object",
        "title": "Cursor page",
        "description": "Envelope shared by every cursor-paginated extension list.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass as `cursor` to fetch the next page. `null` on the last page, and on lists that\npage by taking one extra row rather than by cursor.\n"
          },
          "has_more": {
            "type": "boolean",
            "description": "Whether another page exists."
          },
          "total_count": {
            "type": "integer",
            "description": "Total matches across every page. Additive and **optional**: it is present only where\ncounting is cheap, so a client that shows a count must fall back to counting the page\nit has and treating `has_more` as \"more than that\". `GET /documents`,\n`GET /document_templates` and `GET /bulk_sends/{id}/documents` send it.\n",
            "x-signclad-extension": true
          }
        },
        "required": [
          "next_cursor",
          "has_more"
        ]
      },
      "PaginationMeta": {
        "type": "object",
        "title": "Page numbers",
        "description": "Page-number pagination, used only by `GET /bulk_sends/{id}/documents`. Every other list\nis cursor-paginated.\n",
        "additionalProperties": true,
        "properties": {
          "current_page": {
            "type": "integer"
          },
          "next_page": {
            "type": [
              "integer",
              "null"
            ]
          },
          "previous_page": {
            "type": [
              "integer",
              "null"
            ]
          },
          "total_count": {
            "type": "integer"
          },
          "total_pages": {
            "type": "integer"
          }
        },
        "required": [
          "current_page",
          "total_count",
          "total_pages"
        ]
      },
      "File": {
        "type": "object",
        "title": "File",
        "description": "One file of a document or template. In a request, supply exactly one source:\n`file_url`, `file_base64`, or the additive `file_id` from `POST /files`.\n\nAccepted types: **PDF, DOC, and DOCX**. 25 MB per file, 500 pages per document. Word\nfiles are converted to PDF and normalized on ingest.\n\nThe type is decided by sniffing the bytes, never by the name you give, so a `name` with\na missing or wrong extension does not fail. Anything that is not one of the three answers\n`422 file_rejected`: `Only PDF and Word (.doc, .docx) files are supported.`\n",
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": "string",
            "description": "Display name, extension included.",
            "examples": [
              "loan-agreement.pdf"
            ]
          },
          "file_url": {
            "type": "string",
            "format": "uri",
            "description": "HTTPS URL Signclad fetches once, server-side. Presigned S3 URLs are the normal case;\nthe URL only has to live long enough for the fetch.\n",
            "writeOnly": true
          },
          "file_base64": {
            "type": "string",
            "contentEncoding": "base64",
            "description": "The file bytes, base64-encoded.",
            "writeOnly": true
          },
          "id": {
            "type": "string",
            "description": "The file's id on the way out. Template responses carry it **and** `file_id`, holding\nthe same value; either can be used.\n",
            "readOnly": true,
            "x-signclad-extension": true
          },
          "file_id": {
            "type": "string",
            "description": "Stable file id, returned with document files. Reuses an already uploaded file on writes.",
            "x-signclad-extension": true
          },
          "merge_fields": {
            "type": "object",
            "description": "`${key}` replacements to apply to a DOCX before conversion, so one call could both\ngenerate and send a document.\n\n**Not yet implemented.** The key is accepted and ignored; nothing reads it today. It\nstays in the contract because v1 is additive-only, and it will start working without a\nversion change when it ships.\n",
            "additionalProperties": {
              "type": "string"
            },
            "writeOnly": true,
            "x-signclad-extension": true
          },
          "pages_number": {
            "type": "integer",
            "description": "Page count once ingest finishes. Always `0` in a create response — the count is\ncomputed asynchronously and appears on the next `GET`.\n",
            "readOnly": true
          },
          "status": {
            "type": "string",
            "description": "Ingest state. A document can be sent and signed while its files are processing.",
            "enum": [
              "uploaded",
              "processing",
              "ready",
              "error"
            ],
            "readOnly": true,
            "x-signclad-extension": true
          },
          "preparation_stage": {
            "type": [
              "string",
              "null"
            ],
            "description": "The current safe-to-display ingest step while a file is pending.",
            "enum": [
              "malware_scan",
              "preparing",
              null
            ],
            "readOnly": true,
            "x-signclad-extension": true
          },
          "preparation_retryable": {
            "type": "boolean",
            "description": "Whether preparation can be retried without uploading the original again.",
            "readOnly": true,
            "x-signclad-extension": true
          },
          "error_message": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why ingest failed, when `status` is `error`.",
            "readOnly": true,
            "x-signclad-extension": true
          },
          "preview_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Signed link to the normalized PDF, for a viewer. `null` until `status` is `ready`. The\nexpiry is coarsened to a 15-minute grid, so the value is stable for 15 to 30 minutes\nrather than changing on every read — it can be cached, but not stored.\n",
            "readOnly": true,
            "x-signclad-extension": true
          }
        }
      },
      "PasscodeDelivery": {
        "type": "object",
        "title": "Passcode delivery",
        "description": "Whether Signclad delivers the passcode for you instead of you sharing it. The object is\nalways present on a recipient in a response, even when no passcode is set —\n`{\"enabled\": false, \"methods\": null, \"expire_after_access\": false}`.\n\nSetting `enabled: true` requires that the recipient has a passcode — either sent in the\nsame request or already stored from an earlier one. Enabling delivery for a recipient\nwith no passcode, or sending `passcode: null` alongside it, is refused with `422`:\nwithout a code the recipient would face a gate nothing opens.\n",
        "additionalProperties": true,
        "properties": {
          "enabled": {
            "type": "boolean",
            "default": false
          },
          "methods": {
            "type": [
              "array",
              "null"
            ],
            "description": "Delivery channels. Only `email` is supported today. `null`, not `[]`, when nothing\nwas configured.\n",
            "items": {
              "type": "string",
              "enum": [
                "email"
              ]
            }
          },
          "expire_after_access": {
            "type": "boolean",
            "description": "Invalidate the passcode once it has been used to open the document.",
            "default": false
          }
        }
      },
      "AttachmentRequest": {
        "type": "object",
        "title": "Attachment request",
        "description": "A file the sender asks a recipient to upload before finishing.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "description": "The attachment request's own id. Response only.",
            "readOnly": true
          },
          "name": {
            "type": "string",
            "description": "What to ask for, shown to the signer.",
            "examples": [
              "Proof of insurance"
            ]
          },
          "required": {
            "type": "boolean",
            "default": true
          },
          "recipient_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Which recipient must upload it. `null` on a template, which has placeholders."
          },
          "placeholder_name": {
            "type": "string",
            "description": "On a template, the placeholder that must upload it. Response only.",
            "readOnly": true
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "**Always `null` here.** The document object does not carry download links for\nattachments; fetch them from `GET /documents/{id}/attachments`, where the `Attachment`\nobject carries a real one.\n",
            "readOnly": true
          }
        },
        "required": [
          "name"
        ]
      },
      "Attachment": {
        "type": "object",
        "title": "Attachment",
        "description": "A file a recipient uploaded.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "attachment_request_id": {
            "type": "string"
          },
          "recipient_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "mime": {
            "type": [
              "string",
              "null"
            ],
            "description": "`null` until a file has been uploaded against the request."
          },
          "size": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Bytes. `null` until a file has been uploaded."
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Short-lived download URL. `null` when nothing has been uploaded yet."
          },
          "uploaded_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "CopiedContact": {
        "type": "object",
        "title": "Copied contact",
        "description": "Somebody who receives the completed document but never signs. CC contacts never gate\ncompletion.\n",
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          }
        },
        "required": [
          "email"
        ]
      },
      "Label": {
        "title": "Label",
        "description": "A tag on a document or template. Requests may send a bare string; responses return the\nstored object. `labels` on a template update replaces the whole set.\n",
        "oneOf": [
          {
            "type": "string",
            "description": "The label's name."
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "color": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Hex color used in the web app, or `null` for the default."
              }
            },
            "required": [
              "name"
            ]
          }
        ]
      },
      "FieldOption": {
        "type": "object",
        "title": "Field option",
        "description": "One choice of a `select` or `dropdown` field.",
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": "string",
            "description": "The text the signer sees and the value stored when chosen."
          },
          "api_id": {
            "type": "string",
            "description": "Stable id for this option."
          },
          "is_other": {
            "type": "boolean",
            "description": "True for the free-text \"other\" choice produced by `allow_other`.",
            "default": false
          }
        },
        "required": [
          "name"
        ]
      },
      "Field": {
        "type": "object",
        "title": "Field",
        "description": "A field placed on one page of one file.\n\n**Coordinates.** `x`, `y`, `width`, and `height` are CSS pixels at 96 DPI: one unit is\n`0.75` PDF points. The origin is the top-left corner of the page's visible (crop) box\nand `y` increases downward, so a US Letter page is 816 x 1056 units. To convert:\n\n```\npdf_x_pt            = x * 0.75\npdf_y_from_top_pt   = y * 0.75\npdf_y_bottom_origin = page_height_pt - (y + height) * 0.75\n```\n\nMeasured against the compatibility target, its signing page uses exactly this mapping\nwhile its PDF stamper uses `0.7528125` per unit — 0.37% larger, up to about 2.3 pt of\ndrift across a letter page. Signclad implements 96 DPI everywhere, so a field placed at\n`(x, y)` lands there in the sealed PDF as well as in the viewer.\n\nHeight limits: 200 units for `signature` and `initials`, 74 units for everything else.\nWhen a text tag renders smaller than the minimum usable size for its type, Signclad\ngrows it to that minimum instead of rejecting the document; tags that are already large\nenough render identically to the compatibility target.\n\n**Response order.** Fields come back grouped by type, not in the order they were sent.\nAddress them by `api_id`, never by index.\n",
        "additionalProperties": true,
        "properties": {
          "api_id": {
            "type": "string",
            "description": "Caller-chosen stable id, unique within the document. Field mappings and\n`template_fields` prefill both address fields by this.\n",
            "examples": [
              "borrower_signature"
            ]
          },
          "type": {
            "$ref": "#/components/schemas/FieldType"
          },
          "recipient_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The recipient who fills this field, by their caller-supplied `id`. `null` on a field\nnobody has been assigned to yet, and on every template field.\n"
          },
          "placeholder_id": {
            "type": "string",
            "description": "On a template, the placeholder this field belongs to. Accepted on input; responses\nidentify the placeholder by `placeholder_name` instead and never echo this.\n",
            "writeOnly": true
          },
          "placeholder_name": {
            "type": "string",
            "description": "On a template, the placeholder's name. This is what template responses carry, and\nwhat `recipients[].placeholder_name` matches against.\n"
          },
          "page": {
            "type": "integer",
            "minimum": 1,
            "description": "1-based page within this file."
          },
          "x": {
            "type": "number",
            "description": "Distance from the left edge of the page, in units (1 unit = 0.75 pt).",
            "examples": [
              375
            ]
          },
          "y": {
            "type": "number",
            "description": "Distance from the top edge of the page, in units, increasing downward.",
            "examples": [
              206
            ]
          },
          "width": {
            "type": [
              "string",
              "number"
            ],
            "description": "Width in units. Accepted as a number, **returned as a decimal string** (`\"200.0\"`).\n`x`, `y`, and `page` stay numbers.\n",
            "examples": [
              "200.0"
            ]
          },
          "height": {
            "type": [
              "string",
              "number"
            ],
            "description": "Height in units, with the same string-on-the-way-out behavior as `width`. At most\n200 for signature and initials, 74 otherwise.\n",
            "examples": [
              "50.0"
            ]
          },
          "required": {
            "type": "boolean",
            "description": "Defaults to true for every type except `checkbox`, which defaults to false."
          },
          "label": {
            "type": [
              "string",
              "null"
            ],
            "description": "Shown to the signer, and used to name the CSV column in a bulk send. A text field\nwith no label reads `null`.\n"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal name shown in the builder. `null` when it was never set."
          },
          "value": {
            "description": "The field's value. Prefilled values are set here; signature and initials cannot be\nfilled through the API.\n\nOn the way out: a checked `checkbox` serializes as the string `\"t\"`, not `true`. A\ndate renders through the field's `date_format`, so an ISO value sent as\n`2026-07-02T00:00:00Z` comes back as `\"07/02/2026\"`. An untouched text field is\n`\"\"` on a template and `null` on a document.\n",
            "type": [
              "string",
              "number",
              "boolean",
              "null"
            ]
          },
          "validation": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/FieldValidation"
              },
              {
                "type": "null"
              }
            ],
            "description": "`null` for a field type that does not default one. Which values are available depends\non the plan: the richer validations are a Business feature, and one the plan does not\ninclude is refused at create time.\n"
          },
          "validation_regex": {
            "type": [
              "string",
              "null"
            ],
            "description": "Used when `validation` is `custom`. `null` when it was never set."
          },
          "validation_regex_error_message": {
            "type": [
              "string",
              "null"
            ],
            "description": "Shown to the signer when the regex does not match. `null` when unset."
          },
          "fixed_width": {
            "type": "boolean",
            "description": "Keep the drawn width instead of growing the box to fit the text."
          },
          "lock_sign_date": {
            "type": "boolean",
            "description": "A date field that fills itself with the signing date and cannot be edited. Sending a\n`value` for such a field is ignored with a warning in `warnings[]`, never rejected.\n"
          },
          "date_format": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/DateFormat"
              },
              {
                "type": "null"
              }
            ],
            "description": "`null` for a field type that does not default one."
          },
          "formula": {
            "type": [
              "string",
              "null"
            ],
            "description": "Relative date expression, as used in text tags:\n`created_date|sent_date|signed_date ± N day(s)|week(s)|month(s)`. The key is always\npresent: a date field with no formula reads `\"\"`, and every other type reads `null`.\n",
            "examples": [
              "signed_date + 30 days"
            ]
          },
          "options": {
            "type": "array",
            "description": "Choices for `select` and `dropdown`.",
            "items": {
              "$ref": "#/components/schemas/FieldOption"
            }
          },
          "default_option": {
            "type": [
              "string",
              "null"
            ],
            "description": "The option selected before the signer touches the field. `null` when unset."
          },
          "allow_other": {
            "type": "boolean",
            "description": "Add a free-text \"other\" choice.",
            "default": false
          },
          "signature_meaning": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 60,
            "description": "What signing this field means — \"Approved by\", \"Reviewed by\", \"Author\". 21 CFR Part 11\n§11.50 requires the meaning to be recorded with the signature, and it is printed beside\nit in the sealed PDF. Free text on purpose: the regulation does not enumerate meanings,\nand every industry uses different words.\n"
          },
          "signing_elements_group_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The checkbox group this field belongs to, or `null`. On a **document** this is the\ngroup's id; on a **template** it is the group's `group_name` instead.\n",
            "readOnly": true
          },
          "template_id": {
            "type": "string",
            "format": "uuid",
            "description": "On a document composed from several templates, the template this field came from.\nField identity there is the pair (`template_id`, `api_id`).\n",
            "readOnly": true,
            "x-signclad-extension": true
          },
          "pre_signed": {
            "type": "boolean",
            "description": "True for a signature or initials copied in from the template as content. It is\nvisible, produced no signing event, and does not change any recipient's status.\n",
            "readOnly": true,
            "x-signclad-extension": true
          }
        },
        "required": [
          "type"
        ]
      },
      "CheckboxGroup": {
        "type": "object",
        "title": "Checkbox group",
        "description": "Rules across a set of checkboxes, such as \"choose at least two\". Replaced wholesale by a\ntemplate update.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "group_name": {
            "type": "string"
          },
          "recipient_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Whose checkboxes these are. `null` on a template."
          },
          "placeholder_id": {
            "type": "string",
            "description": "On a template, the placeholder instead of a recipient. Accepted on input; template\nresponses identify the placeholder by `placeholder_name`.\n",
            "writeOnly": true
          },
          "placeholder_name": {
            "type": "string",
            "description": "On a template, the placeholder whose checkboxes these are. Response only.",
            "readOnly": true
          },
          "checkbox_ids": {
            "type": "array",
            "description": "The `api_id` of each checkbox field in the group.",
            "items": {
              "type": "string"
            }
          },
          "validation": {
            "type": [
              "string",
              "null"
            ],
            "description": "Which of `min_value`, `max_value`, and `exact_value` applies: `minimum` uses\n`min_value`, `maximum` uses `max_value`, `exact` uses `exact_value`, and `range`\nuses both bounds. `null` when the group only groups.\n",
            "enum": [
              "minimum",
              "maximum",
              "exact",
              "range",
              null
            ]
          },
          "required": {
            "type": "boolean",
            "default": false
          },
          "min_value": {
            "type": [
              "integer",
              "null"
            ]
          },
          "max_value": {
            "type": [
              "integer",
              "null"
            ]
          },
          "exact_value": {
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "group_name"
        ]
      },
      "Condition": {
        "type": "object",
        "title": "Condition",
        "description": "One clause of a conditional rule. At most five per rule.",
        "additionalProperties": true,
        "properties": {
          "condition_type": {
            "type": "string",
            "description": "How the listed fields combine.",
            "enum": [
              "any_of",
              "all_of",
              "none_of",
              "not_all_of"
            ]
          },
          "status": {
            "type": "string",
            "description": "The state the listed fields must be in.",
            "enum": [
              "completed",
              "not_completed"
            ]
          },
          "join_operator": {
            "type": "string",
            "description": "How this condition joins the next one in the rule.",
            "enum": [
              "and_operator",
              "or_operator"
            ]
          },
          "field_api_ids": {
            "type": "array",
            "description": "The `api_id` of each field the condition watches.",
            "items": {
              "type": "string"
            }
          },
          "position": {
            "type": "integer",
            "description": "Order of this condition within the rule."
          }
        },
        "required": [
          "condition_type",
          "field_api_ids"
        ]
      },
      "ConditionalRule": {
        "type": "object",
        "title": "Conditional rule",
        "description": "Shows, hides, or requires fields based on what has been filled. At most 10 rules per\ndocument, at most 5 conditions per rule.\n\nRules must form a DAG; a cycle is `422 conditional_rules_cycle`. Hidden fields are never\nrequired, their values are cleared at submit, and every decision is recorded in\n`conditional_logic_decisions`. The server re-evaluates every rule on submit, so a\ntampered client cannot skip one.\n\nOn a template update, rules are upserted by `id`; send `deleted: true` to remove one.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "description": "Present on stored rules; send it back to update one."
          },
          "deleted": {
            "type": "boolean",
            "description": "Send `true` with an `id` to delete that rule during a template update.",
            "writeOnly": true
          },
          "target_id": {
            "type": "string",
            "description": "The `api_id` of the field, or the id of the checkbox group, the rule acts on."
          },
          "action_type": {
            "type": "string",
            "enum": [
              "show",
              "hide",
              "require"
            ]
          },
          "action_field_ids": {
            "type": "array",
            "description": "The `api_id` of each field the action applies to.",
            "items": {
              "type": "string"
            }
          },
          "conditions": {
            "type": "array",
            "maxItems": 5,
            "items": {
              "$ref": "#/components/schemas/Condition"
            }
          }
        },
        "required": [
          "action_type"
        ]
      },
      "ConditionalLogicDecision": {
        "type": "object",
        "title": "Conditional logic decision",
        "description": "What the server decided when it re-evaluated the rules, recorded at submit so an audit\ncan explain why a field was empty.\n",
        "additionalProperties": true,
        "properties": {
          "rule_id": {
            "type": "string"
          },
          "field_api_id": {
            "type": "string"
          },
          "action": {
            "type": "string",
            "enum": [
              "shown",
              "hidden",
              "required",
              "not_required",
              "cleared"
            ]
          },
          "decided_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Recipient": {
        "type": "object",
        "title": "Recipient",
        "description": "Somebody who receives the document. `id` is chosen by the caller and is how fields,\ncheckbox groups, attachment requests, and authentication updates address this person.\n\nRecipients are rows, not user accounts: two recipients may share an email address, and\nthe requester's own address may also be a recipient. Both cases are accepted.\n\n`send_email` is honored whether or not the document is embedded. A non-embedded document\nemails everyone by default.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "description": "Caller-chosen id, unique within the document.",
            "examples": [
              "signer-1"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "description": "Email address for the recipient."
          },
          "placeholder_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "On a create-from-template request, the template placeholder this recipient fills.\nReturned on documents that came from a template, and `null` otherwise.\n"
          },
          "passcode": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 4,
            "maxLength": 64,
            "description": "Code the recipient must enter before the document opens. The key is always present\nin a response and is `null` when there is none; the stored value is never echoed.\n\nFour characters is the floor. Shorter than that and the gate is guessable inside the\nper-recipient lockout, which starts after five wrong attempts.\n"
          },
          "passcode_delivery": {
            "$ref": "#/components/schemas/PasscodeDelivery"
          },
          "subject": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255,
            "description": "Overrides the document's subject for this recipient. `null` when it does not."
          },
          "message": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 4000,
            "description": "Overrides the document's message for this recipient. HTML is allowed. `null` when it\ndoes not.\n"
          },
          "send_email": {
            "type": "boolean",
            "description": "Whether Signclad emails this recipient. Defaults to true on a normal document and\nfalse on an embedded one.\n"
          },
          "send_email_delay": {
            "type": "integer",
            "minimum": 0,
            "maximum": 60,
            "description": "Minutes to hold the email, useful for staggering a signing order."
          },
          "email_delivery": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "readOnly": true,
            "x-signclad-extension": true,
            "description": "The first invitation's provider hand-off. `pending` means Signclad is retrying;\n`accepted` means the provider accepted it; `delivered` comes from the provider's\ndelivery webhook; and `failed` or `suppressed` means no invitation was sent.\n",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "pending",
                  "accepted",
                  "delivered",
                  "failed",
                  "suppressed"
                ]
              },
              "queued_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "accepted_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "delivered_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "failed_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "failed_details": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "delivery_method": {
            "$ref": "#/components/schemas/DeliveryMethod"
          },
          "phone_number": {
            "type": "string",
            "description": "Optional contact phone number in E.164 format. Accepted on input and stored; it is\nnot echoed on any response.\n",
            "writeOnly": true,
            "examples": [
              "+14155550142"
            ]
          },
          "role": {
            "type": "string",
            "description": "`signer` gates completion; `cc` only receives the finished document. A recipient\nlisted in `copied_contacts` is the same thing as a `cc` role.\n",
            "enum": [
              "signer",
              "cc"
            ],
            "default": "signer"
          },
          "status": {
            "$ref": "#/components/schemas/RecipientStatus"
          },
          "signing_order": {
            "type": "integer",
            "description": "This recipient's 1-based position, assigned from the order they appear in the request.\nIt is **always** populated, whether or not `apply_signing_order` is set — that flag\ndecides whether the order is enforced, not whether it is recorded. Not read from the\nrequest: to change the order, send the recipients in the order you want.\n",
            "readOnly": true
          },
          "signing_url": {
            "type": "string",
            "format": "uri",
            "description": "The hosted signing link for this recipient, on a non-embedded document. An embedded\nrecipient carries `embedded_signing_url` instead; the two never appear together.\n\nSending a draft regenerates it, so a link captured from a draft response stops\nworking once the document is sent.\n",
            "readOnly": true
          },
          "embedded_signing_url": {
            "type": "string",
            "format": "uri",
            "description": "Single-use URL for the embed script. Present when `embedded_signing` is true.\nRegenerate it with `POST /documents/{id}/recipients/{rid}/signing_url`.\n",
            "readOnly": true
          },
          "bounced": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "`null` until something bounces, then `true`. It is not `false` before a bounce, so\ncheck for truthiness rather than for `false`.\n",
            "readOnly": true
          },
          "bounced_details": {
            "type": [
              "string",
              "null"
            ],
            "description": "The bounce reason reported by the mail provider.",
            "readOnly": true
          },
          "declined_reason": {
            "type": [
              "string",
              "null"
            ],
            "readOnly": true
          },
          "reassigned_from": {
            "type": [
              "string",
              "null"
            ],
            "description": "The `id` of the recipient who handed this turn over.",
            "readOnly": true,
            "x-signclad-extension": true
          },
          "viewed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "readOnly": true
          },
          "signed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "readOnly": true
          },
          "id_verification": {
            "description": "This recipient's own identity-verification requirement. `null` when they simply inherit\nthe document's.\n",
            "oneOf": [
              {
                "$ref": "#/components/schemas/IdVerificationRequest"
              },
              {
                "type": "null"
              }
            ]
          },
          "attachment_requests": {
            "type": "array",
            "description": "The attachments this recipient was asked for.",
            "readOnly": true,
            "items": {
              "$ref": "#/components/schemas/AttachmentRequest"
            }
          }
        },
        "required": [
          "id"
        ]
      },
      "Placeholder": {
        "type": "object",
        "title": "Placeholder",
        "description": "A role on a template. Documents created from the template assign a real recipient to\neach placeholder by `placeholder_name`.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "description": "Caller-chosen id, unique within the template.",
            "examples": [
              "borrower"
            ]
          },
          "name": {
            "type": "string",
            "description": "The placeholder's display name. This is what `recipients[].placeholder_name` and\n`exclude_placeholders[]` match on, and what prefixes bulk send CSV columns.\n",
            "examples": [
              "Borrower"
            ]
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Default recipient address, used when the document does not override it. Accepted on\ninput; responses carry it as `preassigned_recipient_email` instead.\n",
            "writeOnly": true
          },
          "role": {
            "type": "string",
            "description": "`signer` gates completion; `cc` only receives the finished document.",
            "enum": [
              "signer",
              "cc"
            ],
            "default": "signer"
          },
          "preassigned_recipient_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The name a document inherits for this role when it does not name one. `null` when the\ntemplate set none.\n",
            "readOnly": true
          },
          "preassigned_recipient_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "description": "The address form of the same default, and what `email` is echoed back as.",
            "readOnly": true
          },
          "signing_order": {
            "type": "integer"
          },
          "delivery_method": {
            "$ref": "#/components/schemas/DeliveryMethod"
          },
          "subject": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "message": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 4000
          },
          "send_email": {
            "type": "boolean"
          },
          "send_email_delay": {
            "type": "integer",
            "minimum": 0,
            "maximum": 60
          },
          "passcode_delivery": {
            "$ref": "#/components/schemas/PasscodeDelivery"
          },
          "attachment_requests": {
            "type": "array",
            "description": "What this role is asked to upload, carried on the role itself — where a client filling\nthe template looks for it.\n",
            "items": {
              "$ref": "#/components/schemas/AttachmentRequest"
            },
            "readOnly": true
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "CopiedPlaceholder": {
        "type": "object",
        "title": "Copied placeholder",
        "description": "A CC role on a template, filled in the same way as a placeholder.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": "string",
            "format": "email"
          }
        }
      },
      "TemplateFieldValue": {
        "type": "object",
        "title": "Template field prefill",
        "description": "A value to prefill into a template field when creating a document.\n\nDates accept full ISO 8601 (`2026-07-02T00:00:00Z`) and, additively, bare\n`YYYY-MM-DD`, which the compatibility target rejects as\n`{\"errors\":{\"template_fields\":{\"<api_id>\":{\"invalid_date_format\":\"DateField value must\nbe in Iso8601 format.\"}}}}`. A stored date comes back rendered through the field's\n`date_format`, so `2026-07-02T00:00:00Z` reads `\"07/02/2026\"`. Signature and initials\nfields cannot be filled through the API. A value sent for a `lock_sign_date`\nfield is ignored with a warning rather than rejected.\n\nOne bad entry rejects the whole request with `errors.template_fields`, unless\n`skip_invalid_fields: true` is set, in which case bad entries are dropped and listed in\n`warnings[]`.\n",
        "additionalProperties": true,
        "properties": {
          "api_id": {
            "type": "string",
            "description": "The field's `api_id`."
          },
          "template_id": {
            "type": "string",
            "format": "uuid",
            "description": "Required only when composing several templates and the `api_id` appears in more\nthan one of them. A bare ambiguous reference is `422 ambiguous_field_reference`.\n",
            "x-signclad-extension": true
          },
          "value": {
            "type": [
              "string",
              "number",
              "boolean",
              "null"
            ]
          }
        },
        "required": [
          "api_id",
          "value"
        ]
      },
      "DocumentSettings": {
        "type": "object",
        "title": "Document settings",
        "description": "Settings shared by documents, templates, and create-from-template requests.",
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": "string",
            "description": "Internal name. Defaults to the first file's name."
          },
          "subject": {
            "type": "string",
            "maxLength": 255,
            "description": "Subject line of the signing invitation."
          },
          "message": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 4000,
            "description": "Body of the signing invitation. A safe subset of HTML is allowed. `null` when none was\nset and none was inherited.\n"
          },
          "test_mode": {
            "type": "boolean",
            "description": "A test document is excluded from billing and is not legally binding. Sandbox API\nkeys force this on.\n\nTest mode does **not** suppress delivery: a test document emails its recipients like\nany other, and an undeliverable address bounces. To exercise a send that mails\nnobody, use `embedded_signing: true` with `send_email: false` on every recipient.\n",
            "default": false
          },
          "draft": {
            "type": "boolean",
            "description": "Keep the document editable instead of sending it. A draft returns a document-level\n`embedded_edit_url`.\n",
            "default": false
          },
          "self_sign": {
            "type": "boolean",
            "description": "The requester signs immediately rather than being emailed a link.",
            "default": false
          },
          "with_signature_page": {
            "type": "boolean",
            "description": "Append a generated signature page listing every signer, each with a signature field,\na locked date-signed field, and a name autofill. Those fields are addressable as\n`SignaturePage_Signature_<recipient_id>` and the page counts toward `pages_number`.\n",
            "default": false
          },
          "expires_in": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 365,
            "description": "Days until the document expires. Defaults to the template's value, then the\naccount's, which is 30.\n",
            "default": 30
          },
          "reminders": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Automatic reminders on days 3, 6, and 10. `null` on a template that never made a\nstatement either way; always a boolean on a document.\n",
            "default": true
          },
          "apply_signing_order": {
            "type": "boolean",
            "description": "Send to recipients in order; each is notified only once every earlier signer has\nsigned.\n",
            "default": false
          },
          "api_application_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Branding and embed origins to use for this document. `null` when none is set."
          },
          "embedded_signing": {
            "type": "boolean",
            "description": "Return per-recipient `embedded_signing_url`s for use in an iframe.",
            "default": false
          },
          "embedded_signing_notifications": {
            "type": "boolean",
            "description": "Send Signclad's own emails even though the document is embedded.",
            "default": false
          },
          "text_tags": {
            "type": "boolean",
            "description": "Parse Signclad `[type|modifier]` tags or legacy positional `{{...}}` tags in the\nuploaded files into fields. Both formats work for documents and templates and may\nbe mixed in one file. The tag text stays visible in the converted file, so place tags\nwhere a reader will not mind seeing them — white or background-coloured text in the\nsource document is the usual approach.\n",
            "default": false
          },
          "custom_requester_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "description": "Name the invitation comes from. `null` when the workspace default applies."
          },
          "custom_requester_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "description": "Reply-to address for the invitation. `null` when the workspace default applies."
          },
          "requester_email_address": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "description": "Who the document is from, resolved from the credential that created it. `null` when it\ncould not be resolved — an API key whose owner has since been removed, say. Emitted on\ndocuments **and** templates.\n",
            "readOnly": true
          },
          "redirect_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Where a signer lands after finishing. `null` when none is set."
          },
          "decline_redirect_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Where a signer lands after declining. `null` when none is set."
          },
          "allow_decline": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "`null` on a **template** that never made a statement either way, in which case each\ndocument created from it takes the workspace default at that moment. Always a boolean\non a document.\n",
            "default": true
          },
          "allow_reassign": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "`null` on a template that never made a statement, as `allow_decline`. Always a boolean\non a document, and pinned to `false` by `cfr_part11`.\n",
            "default": true
          },
          "language": {
            "$ref": "#/components/schemas/Language"
          },
          "metadata": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Metadata"
              },
              {
                "type": "null"
              }
            ],
            "description": "`null` rather than `{}` when nothing was stored."
          },
          "labels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Label"
            }
          },
          "cfr_part11": {
            "type": "boolean",
            "description": "21 CFR Part 11 mode. It can also be switched on by the workspace or the account, so a\nrequest that never mentions it may still come back with it set.\n\nTurning it on pins `allow_reassign` to `false` — silently, and visibly in the response —\nbecause a Part 11 signature is bound to an identified person and cannot be handed on. A\nPart 11 document also cannot have its audit page omitted.\n"
          },
          "cfr_part11_signing_mode": {
            "type": [
              "string",
              "null"
            ],
            "description": "`streamlined` re-authenticates once for the session; `per_signature` re-authenticates\nfor each signature or initials field. Asking for a mode without the plan feature is\n`422 not_available_on_plan`.\n\nDocuments only. A template accepts `cfr_part11` and this mode at creation and applies\nthem to the documents it makes, but a template response never echoes either back.\n",
            "enum": [
              "streamlined",
              "per_signature",
              null
            ]
          },
          "id_verification": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/IdVerificationRequest"
              },
              {
                "type": "null"
              }
            ],
            "description": "`null` when nothing was asked for. Documents only, like the Part 11 settings: a\ntemplate stores it and never echoes it.\n"
          },
          "skip_invalid_fields": {
            "type": "boolean",
            "description": "Drop `template_fields` entries that cannot be applied and report them in\n`warnings[]` instead of failing the whole request.\n",
            "default": false,
            "writeOnly": true,
            "x-signclad-extension": true
          }
        }
      },
      "DocumentCreateRequest": {
        "title": "Create document",
        "description": "`files` and `recipients` are required. `fields` is a two-dimensional array: one inner\narray per file, in the same order as `files`.\n\nUnknown properties are ignored, never rejected.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/DocumentSettings"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "files": {
                "type": "array",
                "minItems": 1,
                "maxItems": 20,
                "items": {
                  "$ref": "#/components/schemas/File"
                }
              },
              "recipients": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "$ref": "#/components/schemas/Recipient"
                }
              },
              "fields": {
                "type": "array",
                "description": "One array of fields per file, in `files` order.",
                "items": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Field"
                  }
                }
              },
              "attachment_requests": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AttachmentRequest"
                }
              },
              "copied_contacts": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CopiedContact"
                }
              },
              "checkbox_groups": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CheckboxGroup"
                }
              },
              "conditional_rules": {
                "type": "array",
                "maxItems": 10,
                "items": {
                  "$ref": "#/components/schemas/ConditionalRule"
                }
              }
            },
            "required": [
              "files",
              "recipients"
            ]
          }
        ]
      },
      "DocumentSendRequest": {
        "title": "Send document",
        "description": "Settings to apply while sending a draft. Files, recipients, and fields cannot be changed\nhere; use the builder or `PATCH /documents/{id}` first.\n\n`test_mode` and `draft` are set when the document is created. They may be repeated\nhere — sending the value the document already has is accepted and ignored — but not\nchanged: a different value answers `422 immutable_after_creation` rather than being\nsilently discarded. Whether a document is billed, and whether it goes out, is decided\nat creation.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/DocumentSettings"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "attachment_requests": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AttachmentRequest"
                }
              },
              "copied_contacts": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CopiedContact"
                }
              }
            }
          }
        ]
      },
      "DocumentUpdateRequest": {
        "title": "Update draft",
        "description": "Anything that can be set at creation, applied to a draft. Arrays that are present\nreplace their stored counterpart; arrays that are absent are left alone.\n\nTwo exceptions: `test_mode` and `draft` are set when the document is created. They may\nbe repeated here — the stored value is accepted and ignored — but a different value\nanswers `422 immutable_after_creation` rather than being silently discarded. This\noperation never sends; use `POST /documents/{id}/send` for that.\n",
        "x-signclad-extension": true,
        "allOf": [
          {
            "$ref": "#/components/schemas/DocumentSettings"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "archived": {
                "type": "boolean",
                "description": "Files the document away, or brings it back. Sent on its own, it works whatever the\ndocument's state; sent alongside anything else, the draft rule applies to the\nwhole request.\n"
              },
              "files": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/File"
                }
              },
              "recipients": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Recipient"
                }
              },
              "fields": {
                "type": "array",
                "items": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Field"
                  }
                }
              },
              "attachment_requests": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AttachmentRequest"
                }
              },
              "copied_contacts": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CopiedContact"
                }
              },
              "checkbox_groups": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CheckboxGroup"
                }
              },
              "conditional_rules": {
                "type": "array",
                "maxItems": 10,
                "items": {
                  "$ref": "#/components/schemas/ConditionalRule"
                }
              }
            }
          }
        ]
      },
      "DocumentRemindRequest": {
        "type": "object",
        "title": "Remind",
        "description": "Who to remind. Omit `recipients` to remind everybody who has not finished.",
        "additionalProperties": true,
        "properties": {
          "recipients": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "email": {
                  "type": "string",
                  "format": "email"
                },
                "name": {
                  "type": "string"
                }
              },
              "required": [
                "email"
              ]
            }
          }
        }
      },
      "RecipientsUpdateRequest": {
        "type": "object",
        "title": "Update recipients",
        "description": "Corrections keyed by the recipient's caller-supplied `id`. Only `name` and `email` can\nchange, and only for recipients who have not started signing.\n",
        "additionalProperties": true,
        "properties": {
          "recipients": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "email": {
                  "type": "string",
                  "format": "email"
                }
              },
              "required": [
                "id"
              ]
            }
          }
        },
        "required": [
          "recipients"
        ]
      },
      "AuthenticationUpdateRequest": {
        "type": "object",
        "title": "Update authentication",
        "description": "Passcode changes keyed by recipient id. Send `passcode: null` to remove one. A recipient\nwho has already saved a value can still have their passcode changed; nothing else about\nthem can.\n",
        "additionalProperties": true,
        "properties": {
          "recipients": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string"
                },
                "passcode": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "minLength": 4,
                  "maxLength": 64
                },
                "passcode_delivery": {
                  "$ref": "#/components/schemas/PasscodeDelivery"
                }
              },
              "required": [
                "id"
              ]
            }
          }
        },
        "required": [
          "recipients"
        ]
      },
      "EmbeddedBranding": {
        "type": "object",
        "title": "Embedded branding",
        "description": "Branding the embedded pages render with, resolved from the API application.",
        "additionalProperties": true,
        "readOnly": true,
        "properties": {
          "primary_color": {
            "type": "string"
          },
          "button_text_color": {
            "type": "string"
          },
          "link_text_color": {
            "type": "string"
          },
          "buttons_border_radius": {
            "type": "integer"
          },
          "custom_logo_file": {
            "type": [
              "string",
              "null"
            ],
            "description": "The **raw storage key** of the application's logo, not a URL — unlike\n`ApiApplication.custom_logo_file`, which is a short-lived signed link. Same name,\ndifferent thing.\n"
          }
        }
      },
      "Document": {
        "title": "Document",
        "description": "A document and everything on it. Fields marked `x-signclad-extension` are additive and\nabsent from the compatibility target's responses.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/DocumentSettings"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "status": {
                "$ref": "#/components/schemas/DocumentStatus"
              },
              "finalizing": {
                "type": "boolean",
                "description": "True while the last signature has been taken and the seal job is still running. The\nwire `status` reads `Pending` in that window — a sender must never see a\nhalf-completed document — so this is the only way to tell \"waiting on a signer\" from\n\"waiting on us\". Nothing can cancel, expire, reassign, or submit while it is true;\nthose answer `409 document_finalizing`.\n",
                "x-signclad-extension": true
              },
              "archived": {
                "type": "boolean"
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              },
              "updated_at": {
                "type": "string",
                "format": "date-time"
              },
              "completed_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "x-signclad-extension": true
              },
              "expires_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "x-signclad-extension": true
              },
              "decline_message": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The reason the signer gave when declining."
              },
              "error_message": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Why the document is in `Error`, or why sealing has not finished yet."
              },
              "delivery_status": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "sending",
                  "sent",
                  "delivered",
                  "partially_sent",
                  "failed",
                  null
                ],
                "readOnly": true,
                "x-signclad-extension": true,
                "description": "Aggregate state of the initial recipient email hand-offs."
              },
              "delivery_error": {
                "type": [
                  "string",
                  "null"
                ],
                "readOnly": true,
                "x-signclad-extension": true,
                "description": "Provider diagnostics for the first failed initial invitation, when any."
              },
              "embedded_edit_url": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri",
                "description": "Single-use builder URL, returned while the document is a draft; it becomes\n`null` once the document is sent. It is consumed the first time it is opened;\nthe iframe hands the host a replacement in its `setId` message, and\n`POST /documents/{id}/embedded_edit_url` mints one on demand.\n"
              },
              "embedded_preview_url": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri",
                "description": "Read-only embedded view of the document. `null` on a draft; it appears once the\ndocument has been sent.\n\nThere is no document-level embedded signing URL: embedded signing links live on\n`recipients[].embedded_signing_url`, one per recipient, even when the document\nhas exactly one embedded signer.\n"
              },
              "template_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "The template this document came from, when it came from exactly one."
              },
              "template_ids": {
                "type": "array",
                "description": "Every template composed into this document, in order.",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              },
              "embedded": {
                "description": "Branding resolved from the API application, or `null` — which is what it is\nunless the document was created through an API application with branding.\n",
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/EmbeddedBranding"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "files": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/File"
                }
              },
              "recipients": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Recipient"
                }
              },
              "fields": {
                "type": "array",
                "description": "One array per file, in `files` order. Within a file, fields are grouped by type\nrather than kept in the order they were sent; address them by `api_id`.\n",
                "items": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Field"
                  }
                }
              },
              "attachment_requests": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AttachmentRequest"
                }
              },
              "copied_contacts": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CopiedContact"
                }
              },
              "checkbox_groups": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CheckboxGroup"
                }
              },
              "conditional_rules": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ConditionalRule"
                }
              },
              "conditional_logic_decisions": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ConditionalLogicDecision"
                }
              },
              "completed_pdf_url": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri",
                "description": "Short-lived link to the sealed PDF, present once the document is complete.\n\nAdditive: the compatibility target omits this key even on a `Completed`\ndocument, so a compatibility client fetches the bytes from\n`GET /documents/{id}/completed_pdf` instead.\n",
                "x-signclad-extension": true
              },
              "audit_trail_url": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri",
                "description": "Short-lived link to the signed `audit.json` for this document.",
                "x-signclad-extension": true
              },
              "sent_revision_hash": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "SHA-256 of the frozen revision manifest the signers accepted: file hashes, every\nfield definition, recipients and their order, rules, groups, and the settings\nthat change what a signer sees.\n",
                "x-signclad-extension": true
              },
              "source": {
                "type": "string",
                "description": "How the document was created. `mcp` and `zapier` come from `X-Signclad-Source` when\nthe client declared itself; otherwise the credential decides — `oauth` for a bearer\ntoken, `api` for an API key, `web` for a session. `template_link` and `bulk` are set\nby the flow that created the document, and `in_person` by\n`POST /documents/{id}/recipients/{rid}/in_person_session`, which promotes a `web`\ndocument the first time it is signed on the host's own device.\n\n`import` was listed here and never produced by anything, so it has been removed. It\ncomes back when something imports.\n",
                "enum": [
                  "api",
                  "web",
                  "oauth",
                  "template_link",
                  "in_person",
                  "bulk",
                  "mcp",
                  "zapier"
                ],
                "x-signclad-extension": true,
                "examples": [
                  "api",
                  "oauth",
                  "bulk",
                  "template_link"
                ]
              },
              "bulk_send_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "x-signclad-extension": true
              },
              "warnings": {
                "type": "array",
                "description": "Things Signclad accepted but adjusted or ignored. Present only when there were\nany; never a reason to fail.\n",
                "items": {
                  "$ref": "#/components/schemas/Warning"
                },
                "x-signclad-extension": true
              }
            },
            "required": [
              "id",
              "status"
            ]
          }
        ]
      },
      "DocumentList": {
        "title": "Document list",
        "description": "A cursor-paginated page of documents and the count of the whole match.\n",
        "x-signclad-extension": true,
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPage"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Document"
                }
              },
              "total_count": {
                "type": "integer",
                "description": "Every document matching the filters, not just this page."
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "DocumentEvent": {
        "type": "object",
        "title": "Document event",
        "description": "One row of the audit trail. `hash` chains to `prev_hash`, so the chain head proves that\nno earlier row was altered.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "sequence": {
            "type": "integer",
            "description": "Monotonic per document. Sort by this, not by time."
          },
          "type": {
            "type": "string",
            "examples": [
              "recipient_viewed",
              "field_saved",
              "consent_recorded",
              "recipient_signed",
              "document_sealed"
            ]
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "recipient_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "actor": {
            "type": "object",
            "additionalProperties": true,
            "description": "Who caused it: a signer, a workspace member, an API key, or the system.",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "recipient",
                  "user",
                  "api_key",
                  "system"
                ]
              },
              "id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "ip": {
            "type": [
              "string",
              "null"
            ]
          },
          "user_agent": {
            "type": [
              "string",
              "null"
            ]
          },
          "geo": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "description": "Coarse location derived from the IP, city level at best, as a JSON object. `null` when\nnothing could be derived.\n"
          },
          "details": {
            "type": "object",
            "additionalProperties": true,
            "description": "Event-specific payload, such as the field that was saved."
          },
          "prev_hash": {
            "type": [
              "string",
              "null"
            ]
          },
          "hash": {
            "type": "string",
            "description": "`SHA-256(prev_hash || canonical JSON of this row)`."
          }
        },
        "required": [
          "id",
          "sequence",
          "type",
          "occurred_at"
        ]
      },
      "DocumentEventList": {
        "title": "Document event list",
        "description": "A cursor-paginated page of audit events, oldest first.",
        "x-signclad-extension": true,
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPage"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/DocumentEvent"
                }
              },
              "chain_head": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The hash of the newest event, as embedded in the audit page. `null` when the\ndocument has no events yet.\n"
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "CompletedPdfUrl": {
        "type": "object",
        "title": "Completed PDF link",
        "description": "Returned by `completed_pdf` when `url_only=true`.\n\n**These are API routes, not pre-signed links.** `completed_pdf_url` is this endpoint's own\nURL and `audit_trail_url` is the same URL with `?audit_only=true`. Both need the caller's\nown credential, and neither can be handed to a browser or forwarded to somebody else.\nFetch the bytes yourself and serve them, or use a recipient's signing session.\n",
        "additionalProperties": true,
        "properties": {
          "completed_pdf_url": {
            "type": "string",
            "format": "uri"
          },
          "audit_trail_url": {
            "type": "string",
            "format": "uri",
            "x-signclad-extension": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Advisory only. The URLs above carry no expiry of their own — they keep working for as\nlong as the credential does.\n",
            "x-signclad-extension": true
          }
        },
        "required": [
          "completed_pdf_url"
        ]
      },
      "Nom151Certificate": {
        "type": "object",
        "title": "NOM-151 constancia",
        "description": "The parsed constancia, returned by `object_only`. Several fields come straight out of what\nthe certification service provider wrote and are therefore whatever it wrote — `issued_at`\nin particular is only an ISO timestamp when the provider recorded one.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "document_id": {
            "type": "string",
            "format": "uuid"
          },
          "folio": {
            "description": "The PSC's own reference for the constancia."
          },
          "provider": {
            "description": "Which PSC issued it."
          },
          "provider_reference": {
            "description": "The PSC's transaction reference."
          },
          "issued_at": {
            "description": "When the PSC issued it, falling back to when Signclad sealed the artifact. A string in\npractice, but not guaranteed to be ISO 8601 — the PSC's value is passed through.\n"
          },
          "certified_sha256": {
            "description": "The hash the PSC certified."
          },
          "certificate_serial": {
            "type": [
              "string",
              "null"
            ]
          },
          "certificate_chain": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "constancia_filename": {
            "description": "The constancia's name inside the ZIP."
          },
          "constancia_base64": {
            "type": [
              "string",
              "null"
            ],
            "contentEncoding": "base64",
            "description": "The constancia itself, read out of the ZIP. `null` when it cannot be read."
          },
          "zip_sha256": {
            "type": "string"
          },
          "file_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Template": {
        "title": "Template",
        "description": "A reusable document. Placeholders stand in for recipients, and each field belongs to a\nplaceholder instead of a recipient.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/DocumentSettings"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "status": {
                "$ref": "#/components/schemas/TemplateStatus"
              },
              "archived": {
                "type": "boolean"
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              },
              "updated_at": {
                "type": "string",
                "format": "date-time"
              },
              "template_link": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri",
                "description": "Public fill link. Anybody with it can start a document from the template, and it\nworks while the template is still a draft.\n"
              },
              "embedded_edit_url": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri",
                "description": "Single-use template builder URL."
              },
              "placeholders": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Placeholder"
                }
              },
              "copied_placeholders": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CopiedPlaceholder"
                }
              },
              "files": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/File"
                }
              },
              "fields": {
                "type": "array",
                "description": "One array per file. Template fields are addressed by `placeholder_name` where a\ndocument's fields carry `recipient_id`. `placeholder_id` is accepted on the way in\nand never echoed back.\n",
                "items": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Field"
                  }
                }
              },
              "attachment_requests": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AttachmentRequest"
                }
              },
              "checkbox_groups": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CheckboxGroup"
                }
              },
              "conditional_rules": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ConditionalRule"
                }
              },
              "current_revision": {
                "type": "integer",
                "description": "Revision number, incremented by every save. Pass it as `expected_revision` when\nreplacing a file.\n",
                "x-signclad-extension": true
              },
              "source": {
                "type": "string",
                "description": "How the template came to exist.",
                "enum": [
                  "api",
                  "web",
                  "duplicate"
                ],
                "readOnly": true,
                "x-signclad-extension": true
              },
              "imported_from": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": true,
                "description": "Where the template was imported from. Always present, and `null` on a template\nthat was not imported.\n",
                "properties": {
                  "provider": {
                    "type": "string"
                  },
                  "id": {
                    "type": "string"
                  }
                },
                "x-signclad-extension": true
              },
              "warnings": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Warning"
                },
                "x-signclad-extension": true
              }
            },
            "required": [
              "id",
              "status"
            ]
          }
        ]
      },
      "TemplateCreateRequest": {
        "title": "Create template",
        "description": "Like a document, with `placeholders` instead of `recipients`. `fields` is accepted as an\narray of arrays and, for compatibility, as an object keyed by file and then by field.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/DocumentSettings"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "files": {
                "type": "array",
                "minItems": 1,
                "maxItems": 20,
                "items": {
                  "$ref": "#/components/schemas/File"
                }
              },
              "placeholders": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "$ref": "#/components/schemas/Placeholder"
                }
              },
              "copied_placeholders": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CopiedPlaceholder"
                }
              },
              "fields": {
                "description": "Fields per file. Either an array of arrays in `files` order, or an object whose\nkeys are file keys and whose values are objects of fields.\n",
                "oneOf": [
                  {
                    "type": "array",
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Field"
                      }
                    }
                  },
                  {
                    "type": "object",
                    "additionalProperties": {
                      "type": "object",
                      "additionalProperties": {
                        "$ref": "#/components/schemas/Field"
                      }
                    }
                  }
                ]
              },
              "attachment_requests": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AttachmentRequest"
                }
              },
              "checkbox_groups": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CheckboxGroup"
                }
              },
              "conditional_rules": {
                "type": "array",
                "maxItems": 10,
                "items": {
                  "$ref": "#/components/schemas/ConditionalRule"
                }
              }
            },
            "required": [
              "files",
              "placeholders"
            ]
          }
        ]
      },
      "TemplateUpdateRequest": {
        "title": "Update template",
        "description": "Settings only. `labels` and `checkbox_groups` replace the stored set entirely;\n`conditional_rules` are upserted by `id`, with `deleted: true` to remove one.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/DocumentSettings"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "checkbox_groups": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CheckboxGroup"
                }
              },
              "conditional_rules": {
                "type": "array",
                "maxItems": 10,
                "items": {
                  "$ref": "#/components/schemas/ConditionalRule"
                }
              }
            }
          }
        ]
      },
      "TemplateList": {
        "title": "Template list",
        "description": "A cursor-paginated page of templates and the count of the whole match.",
        "x-signclad-extension": true,
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPage"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Template"
                }
              },
              "total_count": {
                "type": "integer",
                "description": "Every template matching the filters, not just this page."
              }
            },
            "required": [
              "data",
              "total_count"
            ]
          }
        ]
      },
      "TemplateDocumentCreateRequest": {
        "title": "Create document from template",
        "description": "Exactly one of `template_id` and `template_ids` is required. Every recipient must name a\nplaceholder. `files` and `fields` append to the template's own; they never replace them.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/DocumentSettings"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "template_id": {
                "type": "string",
                "format": "uuid",
                "description": "The template to use."
              },
              "template_ids": {
                "type": "array",
                "minItems": 1,
                "description": "Several templates composed into one document. Files concatenate in order,\nplaceholders with the same name merge into one recipient, and settings come from\nthe first template unless the request overrides them.\n",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              },
              "recipients": {
                "type": "array",
                "minItems": 1,
                "description": "Each recipient is assigned to a placeholder by `placeholder_name` and inherits\nthat placeholder's fields and settings.\n",
                "items": {
                  "$ref": "#/components/schemas/Recipient"
                }
              },
              "exclude_placeholders": {
                "type": "array",
                "description": "Placeholder **names** to drop, matched by name rather than id. Only roles\nwithout assigned fields may be excluded. Excluding a role with fields returns 422.\n",
                "items": {
                  "type": "string"
                }
              },
              "template_fields": {
                "type": "array",
                "description": "Values to prefill into the template's fields.",
                "items": {
                  "$ref": "#/components/schemas/TemplateFieldValue"
                }
              },
              "conditional_rules": {
                "type": "array",
                "maxItems": 10,
                "items": {
                  "$ref": "#/components/schemas/ConditionalRule"
                }
              },
              "conditional_rules_mode": {
                "type": "string",
                "description": "What to do with the rules the templates carry: `replace` drops them and uses the\nrequest's, `append` keeps them and adds the request's, `none` clears every rule.\n",
                "enum": [
                  "replace",
                  "append",
                  "none"
                ],
                "default": "append"
              },
              "files": {
                "type": "array",
                "description": "Extra files appended after the template's files.",
                "items": {
                  "$ref": "#/components/schemas/File"
                }
              },
              "fields": {
                "type": "array",
                "description": "Fields for the appended files only, one inner array per entry in `files`. Fields\non the template's own files come from the template.\n",
                "items": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Field"
                  }
                }
              },
              "attachment_requests": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AttachmentRequest"
                }
              },
              "copied_contacts": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CopiedContact"
                }
              },
              "checkbox_groups": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CheckboxGroup"
                }
              }
            },
            "required": [
              "recipients"
            ]
          }
        ]
      },
      "BulkSendCreateRequest": {
        "type": "object",
        "title": "Create bulk send",
        "description": "One document per CSV row, from the given templates.",
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": "string",
            "description": "What to call this run in the web app."
          },
          "template_ids": {
            "type": "array",
            "minItems": 1,
            "description": "The templates to compose for every row, in order.",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "bulk_send_csv": {
            "type": "string",
            "contentEncoding": "base64",
            "description": "The CSV, base64-encoded. Columns are `<placeholder_name>_<field_label>`, plus a name\nand email column per placeholder.\n"
          },
          "skip_row_errors": {
            "type": "boolean",
            "description": "Send every valid row and report the rest, instead of refusing the whole file when\nany row is bad.\n",
            "default": false
          },
          "subject": {
            "type": "string",
            "maxLength": 255
          },
          "message": {
            "type": "string",
            "maxLength": 4000
          },
          "apply_signing_order": {
            "type": "boolean",
            "default": false
          },
          "custom_requester_name": {
            "type": "string",
            "maxLength": 100
          },
          "custom_requester_email": {
            "type": "string",
            "format": "email"
          },
          "api_application_id": {
            "type": "string",
            "format": "uuid"
          },
          "test_mode": {
            "type": "boolean",
            "default": false
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        },
        "required": [
          "template_ids",
          "bulk_send_csv"
        ]
      },
      "BulkSendValidateCsvRequest": {
        "type": "object",
        "title": "Validate bulk send CSV",
        "description": "The same CSV and templates as a create, checked without sending anything.",
        "additionalProperties": true,
        "properties": {
          "template_ids": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "bulk_send_csv": {
            "type": "string",
            "contentEncoding": "base64"
          }
        },
        "required": [
          "template_ids",
          "bulk_send_csv"
        ]
      },
      "BulkSendRowError": {
        "type": "object",
        "title": "Bulk send row error",
        "description": "What went wrong on one CSV row. Every entry carries the same fact **twice**: flat as\n`column` and `error`, and keyed by column as `errors`. Read whichever is convenient; they\nnever disagree.\n",
        "additionalProperties": true,
        "properties": {
          "row": {
            "type": "integer",
            "description": "1-based row number, not counting the header."
          },
          "column": {
            "type": "string",
            "description": "The header text exactly as it appeared in the file, or the literal `base` for something\nwrong with the row as a whole.\n"
          },
          "error": {
            "type": "string",
            "description": "A sentence fragment, lower-cased, such as `is not a valid email address.`"
          },
          "errors": {
            "type": "object",
            "description": "The same message, keyed by column. Exactly one key, one message, and the value is a\n**string**: nothing under an `errors` key anywhere in this API is an array, so a client\nhas one reader for error leaves rather than one per endpoint. It was a single-element\narray until the error-envelope pass.\n",
            "additionalProperties": {
              "type": "string"
            }
          }
        },
        "required": [
          "row",
          "column",
          "error",
          "errors"
        ]
      },
      "BulkSendValidationResult": {
        "type": "object",
        "title": "Bulk send validation",
        "description": "The dry-run result.",
        "additionalProperties": true,
        "properties": {
          "valid": {
            "type": "boolean"
          },
          "rows_count": {
            "type": "integer",
            "description": "Data rows read, header excluded."
          },
          "valid_rows_count": {
            "type": "integer",
            "description": "How many would be sent."
          },
          "row_errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BulkSendRowError"
            }
          },
          "unknown_columns": {
            "type": "array",
            "description": "Header columns no placeholder or field claims. Never fatal — they are ignored — but\nusually a typo worth showing.\n",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "valid"
        ]
      },
      "BulkSendCsvTemplate": {
        "type": "object",
        "title": "Bulk send CSV template",
        "description": "Returned when `base64=true`.",
        "additionalProperties": true,
        "properties": {
          "csv_base64": {
            "type": "string",
            "contentEncoding": "base64"
          },
          "columns": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "csv_base64"
        ]
      },
      "BulkSendTemplateSummary": {
        "type": "object",
        "title": "Bulk send template",
        "description": "A template a bulk send used.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/TemplateStatus"
          }
        }
      },
      "BulkSend": {
        "type": "object",
        "title": "Bulk send",
        "description": "One run of a CSV against one or more templates. `status` starts at `Enqueuing` while\nrows are turned into documents.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "Enqueuing",
              "Processing",
              "Completed",
              "Partially completed",
              "Failed",
              "Canceled"
            ]
          },
          "template_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "api_application_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "user_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who started the run."
          },
          "documents_count": {
            "type": "integer",
            "description": "Documents this run will produce — the count of **valid** rows, which is not the number\nof rows in the file when some were skipped. `rows_count` is that number.\n"
          },
          "documents_completed": {
            "type": "integer",
            "description": "Counted live, so it moves while the run is being signed."
          },
          "documents_not_completed": {
            "type": "integer"
          },
          "templates": {
            "type": "array",
            "description": "The templates the run used. A template deleted since is simply absent, so this can be\nshorter than `template_ids`.\n",
            "items": {
              "$ref": "#/components/schemas/BulkSendTemplateSummary"
            }
          },
          "rows_count": {
            "type": "integer",
            "description": "Data rows read from the CSV, header excluded.",
            "x-signclad-extension": true
          },
          "rows_generated": {
            "type": "integer",
            "description": "Rows that became documents.",
            "x-signclad-extension": true
          },
          "rows_failed": {
            "type": "integer",
            "x-signclad-extension": true
          },
          "rows_skipped": {
            "type": "integer",
            "description": "Rows dropped because `skip_row_errors` was set.",
            "x-signclad-extension": true
          },
          "skip_row_errors": {
            "type": "boolean"
          },
          "row_errors": {
            "type": "array",
            "description": "Rows that were skipped. Empty unless the run was created with `skip_row_errors: true` —\nwithout it, bad rows fail the whole request instead.\n",
            "items": {
              "$ref": "#/components/schemas/BulkSendRowError"
            }
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "test_mode": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "status"
        ]
      },
      "BulkSendDocumentsPage": {
        "title": "Bulk send documents page",
        "description": "A page of the documents a bulk send produced, with page-number pagination.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/PaginationMeta"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "documents": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            },
            "required": [
              "documents"
            ]
          }
        ]
      },
      "Hook": {
        "type": "object",
        "title": "Hook",
        "description": "A compatibility webhook callback. Its `id` is the HMAC key for `event.hash`, so keep it.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "callback_url": {
            "type": "string",
            "format": "uri"
          },
          "api_application_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Limits delivery to documents created by that API application. The key is omitted\nentirely from a hook created without one, rather than returned as `null`.\n"
          }
        },
        "required": [
          "id",
          "callback_url"
        ]
      },
      "HookCreateRequest": {
        "type": "object",
        "title": "Create hook",
        "description": "Registers a callback that receives every event.",
        "additionalProperties": true,
        "properties": {
          "callback_url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "**https only**, and deliberately stricter than the compatibility target, which accepts\n`http`. A signed payload sent in plaintext is not signed in any sense that matters.\n"
          },
          "api_application_id": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": [
          "callback_url"
        ]
      },
      "ApiApplicationOwner": {
        "type": "object",
        "title": "API application owner",
        "description": "The person who owns the application, falling back to the account's owner when nobody was\nrecorded. Always present, never partial.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "account_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The billing account behind the application's workspace — not a user id."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          }
        }
      },
      "ApiApplication": {
        "type": "object",
        "title": "API application",
        "description": "Branding and callback configuration for an embedded integration. Applications are\ncreated in the web app; the API can read and delete them.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "callback_urls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "primary_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "button_text_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "link_text_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "buttons_border_radius": {
            "type": [
              "integer",
              "null"
            ]
          },
          "custom_logo_file": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "A short-lived signed URL, so it differs between reads and is `null` on a storage\ndriver that cannot sign. See `EmbeddedBranding.custom_logo_file`, which carries the\nraw key instead.\n"
          },
          "default_webhook_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The webhook endpoint documents from this application deliver to by default.",
            "x-signclad-extension": true
          },
          "allowed_embed_domains": {
            "type": "array",
            "description": "Origins allowed to frame this application's embedded pages, enforced through\n`frame-ancestors`.\n",
            "items": {
              "type": "string"
            },
            "x-signclad-extension": true
          },
          "owner": {
            "$ref": "#/components/schemas/ApiApplicationOwner"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "MeUser": {
        "type": "object",
        "title": "User",
        "description": "The person behind the credential. `first_name` and `last_name` come from one resolver — the\ncolumns when they are set, a split of `name` on the first space when they are not — so they\ncan never disagree with `name` or with each other. `last_name` is `null` for a\nsingle-word name.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "has_google_registration": {
            "type": "boolean"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "email_verified": {
            "type": "boolean",
            "description": "Whether this account has proved control of its email address.",
            "x-signclad-extension": true
          },
          "locale": {
            "type": [
              "string",
              "null"
            ]
          },
          "timezone": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "MeAccountUser": {
        "type": "object",
        "title": "Account seat",
        "description": "A member who counts against the account's seats.\n\n`id` is the **user's** id, not the membership's — unlike `Me.id`, which is the\nmembership. A seat whose user row has gone answers `null` for name and email rather than\ndisappearing from the list.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "has_google_registration": {
            "type": "boolean"
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "member",
              "viewer"
            ]
          }
        }
      },
      "MeAccount": {
        "type": "object",
        "title": "Account",
        "description": "Billing owner. Plans, seats, and allowances live here, not on the workspace.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "plan_tier": {
            "type": "string",
            "enum": [
              "free",
              "light",
              "business",
              "enterprise"
            ]
          },
          "active_templates": {
            "type": "integer"
          },
          "can_create_template": {
            "type": "boolean",
            "description": "Whether the plan's template cap has room for one more. `true` when the plan is\nunlimited, when this deployment has no plan matrix installed, or when the workspace's\nlive (non-archived) templates are under the cap.\n"
          },
          "can_create_tracking_document": {
            "type": "boolean",
            "description": "Whether the account's monthly document allowance has anything left. Read from the same\ncap and the same counter that would refuse the send, so a `false` here and the `422\ndocument_limit_reached` on the next create cannot disagree.\n\nBoth were hard-coded `true` while plan gating was landing, which made them worse than\nabsent: a client that hid its \"new document\" button on `false` never hid it, and the\ncustomer met the cap as an error instead.\n"
          },
          "can_create_completion_document": {
            "type": "boolean",
            "description": "The same allowance as `can_create_tracking_document`, and therefore always the same\nanswer: Signclad meters one document cap, not two. A document that only tracks and a\ndocument that collects signatures each spend one of the plan's `documents_per_month`.\n"
          },
          "active_users": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MeAccountUser"
            }
          }
        }
      },
      "MeWorkspace": {
        "title": "Workspace",
        "description": "**The same object as `account`, byte for byte.** `GET /me` builds one body and puts it\nunder both keys, because that is what the compatibility target does and a client reading\neither key must get something usable.\n\nSo it carries the account's keys — `id`, `name`, `plan_tier`, `active_templates`, the\nthree capability flags, `active_users` — and **not** a workspace's own. There is no\n`slug` and no `test_mode` here.\n\nThe real tenant id is `current_workspace_id` on the envelope. Read that when you need the\nworkspace documents and API keys actually belong to; this key is the account.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/MeAccount"
          }
        ]
      },
      "MeContact": {
        "type": "object",
        "title": "Contact",
        "description": "The address-book entry matching the actor, used to autofill this sender's own fields.\n`null` when the workspace has no contact at the actor's address.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "company": {
            "type": [
              "string",
              "null"
            ]
          },
          "job_title": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone_number": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Me": {
        "type": "object",
        "title": "Me",
        "description": "The membership the credential resolves to.",
        "additionalProperties": true,
        "properties": {
          "current_workspace_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The workspace this request actually resolved to, and the tenant documents, templates,\nAPI keys and webhooks belong to. `workspace.id` is the compatibility **account** id\nand is not this.\n",
            "x-signclad-extension": true
          },
          "editor_features": {
            "type": "object",
            "description": "Server-resolved permissions for paid editor controls. Basic checkbox groups are available on every plan.",
            "properties": {
              "conditional_rules": {
                "type": "boolean"
              },
              "data_validation": {
                "type": "boolean"
              }
            }
          },
          "id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The membership's id, or the API key's id when the credential is a key. `null` when\nneither resolves.\n"
          },
          "role": {
            "type": [
              "string",
              "null"
            ],
            "description": "`admin` for an API key, which is how the recorded shape reports one.",
            "enum": [
              "owner",
              "admin",
              "member",
              "viewer",
              null
            ]
          },
          "archived": {
            "type": "boolean"
          },
          "is_admin": {
            "type": "boolean",
            "description": "Whether this person is Signclad platform staff — what the web app reads to offer the\ninternal console. Always `false` for an API key, which is a credential and not a\nperson.\n",
            "x-signclad-extension": true
          },
          "user": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/MeUser"
              },
              {
                "type": "null"
              }
            ],
            "description": "`null` when the credential resolves to no person."
          },
          "account": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/MeAccount"
              },
              {
                "type": "null"
              }
            ],
            "description": "`null` when no workspace and account could be resolved."
          },
          "workspace": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/MeWorkspace"
              },
              {
                "type": "null"
              }
            ],
            "description": "The same object as `account`, or `null` alongside it."
          },
          "contact": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/MeContact"
              },
              {
                "type": "null"
              }
            ],
            "description": "`null` when the workspace has no contact at the actor's address."
          },
          "impersonation": {
            "description": "Present only while a staff member is impersonating this person, and injected on this\nroute alone — it is how the app knows to show the banner and the way out.\n",
            "oneOf": [
              {
                "$ref": "#/components/schemas/Impersonation"
              },
              {
                "type": "null"
              }
            ],
            "x-signclad-extension": true
          }
        },
        "required": [
          "id"
        ]
      },
      "UploadedFile": {
        "type": "object",
        "title": "Uploaded file",
        "description": "A file stored by `POST /files` and referenced later as `files[].file_id`.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "mime": {
            "type": [
              "string",
              "null"
            ],
            "description": "`null` until ingest has identified the file."
          },
          "size": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Bytes. `null` until ingest has measured the file."
          },
          "preview_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Short-lived link to a rendered preview of the first page, or `null` while the file is\nstill being prepared.\n",
            "readOnly": true
          },
          "status": {
            "type": "string",
            "enum": [
              "uploaded",
              "processing",
              "ready",
              "error"
            ]
          },
          "preparation_stage": {
            "type": [
              "string",
              "null"
            ],
            "description": "The current safe-to-display ingest step while a file is pending.",
            "enum": [
              "malware_scan",
              "preparing",
              null
            ]
          },
          "preparation_retryable": {
            "type": "boolean",
            "description": "Whether preparation can be retried without uploading the original again."
          },
          "pages_number": {
            "type": "integer",
            "description": "`0` until ingest finishes."
          },
          "error_message": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "status"
        ]
      },
      "CancelRequest": {
        "type": "object",
        "title": "Cancel",
        "description": "Why the document was canceled. The reason is recorded in the audit trail.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "reason": {
            "type": "string"
          },
          "notify_recipients": {
            "type": "boolean",
            "description": "Email everybody who had not finished.",
            "default": true
          }
        }
      },
      "CompleteRequest": {
        "type": "object",
        "title": "Manually complete",
        "description": "End signing early and seal what has been signed.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "reason": {
            "type": "string"
          },
          "notify_recipients": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "ReassignRequest": {
        "type": "object",
        "title": "Reassign",
        "description": "Who takes this recipient's turn.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "reason": {
            "type": "string"
          },
          "message": {
            "type": "string",
            "description": "Note included in the new recipient's invitation."
          }
        },
        "required": [
          "email"
        ]
      },
      "SigningUrlRequest": {
        "type": "object",
        "title": "Signing URL request",
        "description": "Options for the regenerated signing URL.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "expires_in_minutes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1440,
            "default": 15,
            "description": "**Echoed, not applied.** The value comes back in the response's `expires_at`, but the\nlink itself always expires after the deployment's configured exchange window — 15\nminutes by default. Do not rely on a longer window than that.\n"
          }
        }
      },
      "InPersonSession": {
        "type": "object",
        "title": "In-person session",
        "description": "A signer bearer token minted by the sender for a device they are handing over. Identical in\nshape and lifetime to what `POST /signer/exchange` returns, minus the compliance flags,\nwhich the signer app reads from `GET /signer/session`.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "token": {
            "type": "string",
            "description": "Present as `Authorization: Bearer <token>`. Shown once and never again."
          },
          "token_type": {
            "type": "string",
            "const": "Bearer"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "scope": {
            "type": "string",
            "const": "in_person"
          },
          "document_id": {
            "type": "string",
            "format": "uuid"
          },
          "recipient_id": {
            "type": "string",
            "description": "The recipient's `id`, as it appears on the document."
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "token",
          "token_type",
          "expires_at",
          "scope",
          "document_id",
          "recipient_id",
          "session_id"
        ]
      },
      "SigningUrlResult": {
        "type": "object",
        "title": "Signing URL",
        "description": "A fresh single-use signing URL for one recipient.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "embedded_signing_url": {
            "type": "string",
            "format": "uri",
            "description": "For use inside an iframe with the embed script."
          },
          "signing_url": {
            "type": "string",
            "format": "uri",
            "description": "The hosted equivalent, for an email or a redirect."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "embedded_signing_url",
          "signing_url",
          "expires_at"
        ]
      },
      "EmbeddedEditUrlRequest": {
        "type": "object",
        "title": "Builder URL request",
        "description": "Options for the regenerated builder URL. Both are accepted and neither currently changes\nthe URL that comes back.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "start": {
            "type": "string",
            "description": "Which view the builder opens on. **Not read here** — it is a parameter of the embed\nscript, which you pass when you mount the iframe, not of this call.\n",
            "enum": [
              "document_view",
              "edit_recipients",
              "edit_files"
            ]
          },
          "expires_in_minutes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1440,
            "default": 15,
            "description": "**Echoed, not applied**, exactly as on `SigningUrlRequest`: it sets the reported\n`expires_at` while the link honours the configured exchange window.\n"
          }
        }
      },
      "EmbeddedEditUrlResult": {
        "type": "object",
        "title": "Builder URL",
        "description": "A fresh single-use builder URL.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "embedded_edit_url": {
            "type": "string",
            "format": "uri"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "embedded_edit_url",
          "expires_at"
        ]
      },
      "DuplicateTemplateRequest": {
        "type": "object",
        "title": "Duplicate template",
        "description": "Options for the copy.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "name": {
            "type": "string",
            "description": "Name for the copy. Defaults to the original's name with a suffix."
          },
          "include_labels": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "FileReplaceRequest": {
        "type": "object",
        "title": "Replace file",
        "description": "The new file, how to move the existing fields onto it, and which revision the caller\nbelieves it is changing.\n\nSupply exactly one of `file_url`, `file_base64`, or `file_id`.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "name": {
            "type": "string"
          },
          "file_url": {
            "type": "string",
            "format": "uri"
          },
          "file_base64": {
            "type": "string",
            "contentEncoding": "base64"
          },
          "file_id": {
            "type": "string"
          },
          "strategy": {
            "type": "string",
            "description": "How fields find their place on the new file. `keep_positions` keeps page and\ncoordinates; `text_tags` re-parses the tags and matches by `api_id`; `anchors` finds\nthe words each field was placed next to and moves the field with them. Anchors are\ncaptured for every field at creation, so the strategy is always available.\n",
            "enum": [
              "keep_positions",
              "text_tags",
              "anchors"
            ],
            "default": "anchors"
          },
          "scale_to_page": {
            "type": "boolean",
            "description": "Scale coordinates when the new page size differs from the old one.",
            "default": true
          },
          "dry_run": {
            "type": "boolean",
            "description": "Report what would happen and change nothing.",
            "default": false
          },
          "expected_revision": {
            "type": "integer",
            "description": "The revision the dry run reported. A commit against a stale revision is\n`409 revision_mismatch`.\n"
          }
        }
      },
      "FileReplaceFieldReport": {
        "type": "object",
        "title": "Field placement",
        "description": "What happened to one field during a replacement.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "api_id": {
            "type": "string"
          },
          "page": {
            "type": "integer"
          },
          "x": {
            "type": "number"
          },
          "y": {
            "type": "number"
          },
          "previous_page": {
            "type": "integer"
          },
          "previous_x": {
            "type": "number"
          },
          "previous_y": {
            "type": "number"
          },
          "reason": {
            "type": "string",
            "description": "Why it moved, was scaled, or was orphaned."
          }
        },
        "required": [
          "api_id"
        ]
      },
      "FileReplaceReport": {
        "type": "object",
        "title": "Replacement report",
        "description": "Every field sorted into what happened to it. `orphaned` fields have no home on the new\nfile, usually because their page is gone; they are kept unplaced until somebody moves\nthem.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "kept": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FileReplaceFieldReport"
            }
          },
          "moved": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FileReplaceFieldReport"
            }
          },
          "scaled": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FileReplaceFieldReport"
            }
          },
          "orphaned": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FileReplaceFieldReport"
            }
          }
        },
        "required": [
          "kept",
          "moved",
          "scaled",
          "orphaned"
        ]
      },
      "FileReplaceResult": {
        "type": "object",
        "title": "Replacement result",
        "description": "The report, plus the revision it applies to. On a dry run, `revision` is the revision to\npass back as `expected_revision`; on a commit, it is the revision that was just created.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "revision": {
            "type": "integer"
          },
          "dry_run": {
            "type": "boolean"
          },
          "requires_review": {
            "type": "boolean",
            "description": "True when anything was orphaned or moved far enough to be worth a look."
          },
          "report": {
            "$ref": "#/components/schemas/FileReplaceReport"
          },
          "file": {
            "$ref": "#/components/schemas/File"
          },
          "warnings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Warning"
            }
          }
        },
        "required": [
          "revision",
          "report"
        ]
      },
      "WebhookEndpoint": {
        "type": "object",
        "title": "Webhook endpoint",
        "description": "A receiver with its own secret, subscriptions, and health. Endpoints created through the\ncompatibility `POST /hooks` route appear here subscribed to every event and active.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "event_types": {
            "type": "array",
            "description": "Subscribed events. An empty array means every event, now and later — which is what a\ncompatibility hook has, and what most receivers want.\n",
            "items": {
              "$ref": "#/components/schemas/EventType"
            }
          },
          "events": {
            "type": "array",
            "description": "Alias of `event_types`, returned alongside it and accepted on the way in, because\nthat is what the field is called in the app.\n",
            "items": {
              "$ref": "#/components/schemas/EventType"
            }
          },
          "api_application_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "include_test_mode": {
            "type": "boolean",
            "default": false
          },
          "active": {
            "type": "boolean",
            "description": "Convenience mirror of `status == \"active\"`."
          },
          "compatibility": {
            "type": "boolean",
            "description": "True for an endpoint registered through `POST /hooks`. Those appear in both listings\nand behave slightly differently: every event, test mode always included, active\nimmediately with no ping.\n"
          },
          "status": {
            "type": "string",
            "description": "`active` delivers; `paused` queues for replay; `disabled` was turned off after five\ndays of continuous failure and drops events until it is re-enabled.\n",
            "enum": [
              "active",
              "paused",
              "disabled"
            ]
          },
          "health": {
            "$ref": "#/components/schemas/WebhookEndpointHealth"
          },
          "secret_last_rotated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "url",
          "status"
        ]
      },
      "WebhookEndpointWithSecret": {
        "title": "Webhook endpoint with secret",
        "description": "The endpoint plus its signing secret. The secret is shown on creation and rotation only\nand cannot be read back.\n",
        "x-signclad-extension": true,
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookEndpoint"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "secret": {
                "type": "string",
                "description": "Signing secret for `X-Signclad-Signature`.",
                "examples": [
                  "whsec_9f8e7d6c5b4a39281706f5e4d3c2b1a0"
                ]
              },
              "ping": {
                "$ref": "#/components/schemas/WebhookPingResult"
              }
            },
            "required": [
              "secret"
            ]
          }
        ]
      },
      "WebhookEndpointCreateRequest": {
        "type": "object",
        "title": "Create webhook endpoint",
        "description": "A new receiver.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "**https only.** A plaintext callback is refused; a signed payload over `http` is not signed."
          },
          "description": {
            "type": "string",
            "maxLength": 255
          },
          "event_types": {
            "type": "array",
            "description": "Leave empty, or send `[\"*\"]`, to subscribe to every event including ones added later.\nEvery other value is checked against the catalog `GET /events` returns, so an event\nthis deployment cannot emit is `422` rather than a subscription that never fires.\n",
            "items": {
              "type": "string"
            }
          },
          "events": {
            "type": "array",
            "description": "Alias of `event_types`; either key is accepted.",
            "items": {
              "type": "string"
            }
          },
          "api_application_id": {
            "type": "string",
            "format": "uuid"
          },
          "include_test_mode": {
            "type": "boolean",
            "default": false
          },
          "secret": {
            "type": "string",
            "minLength": 32,
            "maxLength": 255,
            "description": "Your own signing key, instead of one Signclad mints. For a team migrating from another\nprovider, or one whose receiver already holds a secret in its configuration, this makes\nthe cutover a single deploy rather than a deploy plus a rotation. 32 characters is the\nfloor: the secret is an HMAC key, and a short one is a weak one.\n\nOmit it and a strong secret is generated and returned once.\n",
            "x-signclad-extension": true
          },
          "active": {
            "type": "boolean",
            "description": "Start paused by sending `false`.",
            "default": true
          },
          "verify": {
            "type": "boolean",
            "description": "Send a `ping` first and activate only on a `2xx`. The endpoint is created **paused**\nand becomes active when the ping succeeds; a ping that fails leaves it paused.\n\nLeft `false`, the endpoint is active from the moment it is created and no ping is\nsent.\n",
            "default": false
          }
        },
        "required": [
          "url"
        ]
      },
      "WebhookEndpointUpdateRequest": {
        "type": "object",
        "title": "Update webhook endpoint",
        "description": "Only the properties present are changed.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "**https only**, as on create."
          },
          "description": {
            "type": "string",
            "maxLength": 255
          },
          "event_types": {
            "type": "array",
            "description": "Replaces the whole subscription. `[\"*\"]` means every event including ones added\nlater; every other value is checked against the catalog.\n",
            "items": {
              "type": "string"
            }
          },
          "events": {
            "type": "array",
            "description": "Alias of `event_types`.",
            "items": {
              "type": "string"
            }
          },
          "api_application_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "include_test_mode": {
            "type": "boolean"
          },
          "active": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused"
            ]
          }
        }
      },
      "WebhookEndpointSecret": {
        "type": "object",
        "title": "Webhook secret",
        "description": "A newly issued secret and the overlap window for the previous one.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "secret": {
            "type": "string"
          },
          "previous_secret_expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "secret"
        ]
      },
      "WebhookEndpointHealth": {
        "type": "object",
        "title": "Endpoint health",
        "description": "The current health state and the counters behind it.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "healthy",
              "degraded",
              "failing",
              "disabled"
            ]
          },
          "consecutive_failures": {
            "type": "integer"
          },
          "success_rate_1h": {
            "type": [
              "number",
              "null"
            ],
            "description": "Fraction between 0 and 1, or `null` when nothing was attempted in the window — which\nis not the same fact as a rate of zero.\n"
          },
          "success_rate_24h": {
            "type": [
              "number",
              "null"
            ]
          },
          "p95_latency_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "last_success_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_failure_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_failure_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "pending_count": {
            "type": "integer",
            "description": "Deliveries queued or waiting for their next retry."
          },
          "failing_since": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the current failing streak started. Five days of it disables the endpoint."
          },
          "next_probe_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "While `failing`, when the hourly probe next runs."
          }
        },
        "required": [
          "state"
        ]
      },
      "WebhookDeliveryAttempt": {
        "type": "object",
        "title": "Delivery attempt",
        "description": "One HTTP attempt at a delivery.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "attempt": {
            "type": "integer"
          },
          "attempted_at": {
            "type": "string",
            "format": "date-time"
          },
          "status_code": {
            "type": [
              "integer",
              "null"
            ]
          },
          "latency_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Transport-level failure, when there was no response at all."
          },
          "response_excerpt": {
            "type": [
              "string",
              "null"
            ],
            "description": "First 2 KB of the response body."
          }
        },
        "required": [
          "attempt",
          "attempted_at"
        ]
      },
      "WebhookDelivery": {
        "type": "object",
        "title": "Webhook delivery",
        "description": "One event on its way to one endpoint, with the payload that was sent and every attempt\nmade. Kept 30 days.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "webhook_endpoint_id": {
            "type": "string"
          },
          "event_id": {
            "type": "string",
            "description": "The stable `event.id`. Identical across retries and replays."
          },
          "event_type": {
            "$ref": "#/components/schemas/EventType"
          },
          "document_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "delivered",
              "failed",
              "dead"
            ]
          },
          "attempt_count": {
            "type": "integer"
          },
          "status_code": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The last attempt's response status."
          },
          "latency_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Transport-level failure on the last attempt, when there was no response."
          },
          "replay_of_delivery_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set when this delivery is a replay of another."
          },
          "attempts": {
            "type": "array",
            "description": "Detail view only.",
            "items": {
              "$ref": "#/components/schemas/WebhookDeliveryAttempt"
            }
          },
          "response_excerpt": {
            "type": [
              "string",
              "null"
            ],
            "description": "Detail view only. First 2 KB of the last response body."
          },
          "next_retry_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "request_headers": {
            "type": "object",
            "description": "Detail view only.",
            "additionalProperties": {
              "type": "string"
            }
          },
          "request_body": {
            "description": "Detail view only — the exact payload that was sent.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/WebhookEventEnvelope"
              },
              {
                "type": "null"
              }
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "event_id",
          "event_type",
          "status"
        ]
      },
      "WebhookDeliveryList": {
        "title": "Delivery list",
        "description": "A cursor-paginated page of deliveries.",
        "x-signclad-extension": true,
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPage"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookDelivery"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "WebhookEndpointList": {
        "title": "Endpoint list",
        "description": "A cursor-paginated page of endpoints.",
        "x-signclad-extension": true,
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPage"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "WebhookPingResult": {
        "type": "object",
        "title": "Ping result",
        "description": "What the endpoint answered to a synchronous test delivery.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "delivered": {
            "type": "boolean"
          },
          "status_code": {
            "type": [
              "integer",
              "null"
            ]
          },
          "latency_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "response_excerpt": {
            "type": [
              "string",
              "null"
            ]
          },
          "delivery_id": {
            "type": "string"
          }
        },
        "required": [
          "delivered"
        ]
      },
      "EventCatalogEntry": {
        "type": "object",
        "title": "Event catalog entry",
        "description": "One event type, what triggers it, and what it carries.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "type": {
            "$ref": "#/components/schemas/EventType"
          },
          "summary": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "description": "What `data.object` holds for this event.",
            "enum": [
              "document",
              "template",
              "bulk_send"
            ]
          },
          "compatibility": {
            "type": "boolean",
            "description": "Whether the compatibility target emits this event too."
          },
          "carries_related_signer": {
            "type": "boolean",
            "description": "Whether `event.related_signer` is populated for this event."
          }
        },
        "required": [
          "type",
          "summary"
        ]
      },
      "RelatedSigner": {
        "type": "object",
        "title": "Related signer",
        "description": "The signer an event is about, in the compatibility shape. Present on `document_signed`\nand the other per-signer events. Either key is `null` where the recipient row carried no\nvalue for it.\n",
        "additionalProperties": true,
        "properties": {
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "RelatedRecipient": {
        "type": "object",
        "title": "Related recipient",
        "description": "The recipient an event is about, in the richer shape used by bounce events.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone_number_masked": {
            "type": "string"
          },
          "delivery_method": {
            "$ref": "#/components/schemas/DeliveryMethod"
          }
        }
      },
      "WebhookEventInfo": {
        "type": "object",
        "title": "Event",
        "description": "The event header. `hash` is\n`hex(HMAC-SHA256(key = <hook id>, data = \"<type>@<time>\"))`, the compatibility\nverification scheme; the body signature in `X-Signclad-Signature` is stronger and is\nwhat new receivers should check.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id. Identical across every retry and replay of this event, so it is the\nright key to dedupe on.\n",
            "x-signclad-extension": true
          },
          "hash": {
            "type": "string",
            "description": "Compatibility HMAC over `\"<type>@<time>\"`, keyed by the hook id."
          },
          "time": {
            "type": "integer",
            "description": "Unix timestamp of when the event happened."
          },
          "type": {
            "$ref": "#/components/schemas/EventType"
          },
          "sequence": {
            "type": "integer",
            "description": "Monotonic per document. Deliveries are dispatched in this order but retried\nindependently, so a receiver that cares about order sorts by it.\n",
            "x-signclad-extension": true
          },
          "related_signer": {
            "$ref": "#/components/schemas/RelatedSigner"
          },
          "related_recipient": {
            "$ref": "#/components/schemas/RelatedRecipient"
          }
        },
        "required": [
          "hash",
          "time",
          "type"
        ]
      },
      "WebhookEventData": {
        "type": "object",
        "title": "Event data",
        "description": "The object the event is about, snapshotted when the event happened. Later edits never\nrewrite it.\n",
        "additionalProperties": true,
        "properties": {
          "object": {
            "description": "The full document or template, in the same shape the API returns.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/Document"
              },
              {
                "$ref": "#/components/schemas/Template"
              }
            ]
          },
          "account_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The billing account behind the workspace. The key is always present, and is `null` for\na workspace that has no account.\n"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "description": "Which workspace the object belongs to. Present on every event, alongside\n`account_id`.\n"
          }
        },
        "required": [
          "object",
          "account_id"
        ]
      },
      "WebhookEventEnvelope": {
        "type": "object",
        "title": "Webhook payload",
        "description": "The body of every webhook delivery.",
        "additionalProperties": true,
        "properties": {
          "event": {
            "$ref": "#/components/schemas/WebhookEventInfo"
          },
          "data": {
            "$ref": "#/components/schemas/WebhookEventData"
          }
        },
        "required": [
          "event",
          "data"
        ]
      },
      "VerifyMatch": {
        "type": "object",
        "title": "Verification match",
        "description": "One sealed artifact whose hash is the one that was asked about.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "document_id": {
            "type": "string",
            "format": "uuid"
          },
          "artifact": {
            "type": "string",
            "description": "Which artifact matched. `completed` is the canonical one, stamped pages plus the audit\npage; `completed_no_audit` is the separately generated and sealed variant;\n`completed_lt` is the PAdES long-term-validation enrichment of `completed`, which is\nwhat `GET /documents/{id}/completed_pdf` serves to a workspace entitled to it — so its\nhash has to verify, or the file Signclad handed the customer would report as not one of\nours. The `zip`, `audit_json` and `nom151` artifacts a document may also carry are not\nhashed against here.\n",
            "enum": [
              "completed",
              "completed_no_audit",
              "completed_lt"
            ]
          },
          "sealed_at": {
            "type": "string",
            "format": "date-time"
          },
          "timestamp_valid": {
            "type": "boolean",
            "description": "Whether the RFC 3161 timestamp on the seal still validates. A `completed_lt` match\ninherits this from the `completed` artifact it was appended to: the enrichment is an\nincremental update and carries the same signature timestamp in the same bytes.\n"
          },
          "chain_head": {
            "type": "string",
            "description": "The audit chain head at the moment of sealing."
          },
          "timestamp": {
            "type": "object",
            "title": "Timestamp state",
            "additionalProperties": true,
            "description": "How this file came by its trusted time, disclosed rather than reduced to a boolean.\n\n`embedded` means the RFC 3161 token is inside the PAdES signature, obtained while\nsealing. `detached` means the timestamp authority was unreachable at that moment, so\nthe document was sealed without it and the token was fetched afterwards and stored\nbeside the artifact as a detached `.tsr` over its SHA-256 — the seal itself is\nunchanged, and `attached_at` says when the trusted time arrived. `pending` means the\ntoken is still owed: the signature is valid, the trusted time is not there yet.\n`disabled` means this deployment does not timestamp at all, so nothing is coming.\n\n\"Timestamped\" and \"timestamped an hour later, by a separate token\" are different\nfacts about a document, and the counterparty checking a signature years from now is\nexactly the person entitled to know which one they hold.\n",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "embedded",
                  "detached",
                  "pending",
                  "disabled"
                ]
              },
              "attached_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "When a detached token was attached. Null for every other status."
              },
              "source": {
                "type": "string",
                "description": "Which artifact's token answers for this match. Itself, except for `completed_lt`,\nwhich inherits the timestamp of the `completed` file it was appended to.\n"
              }
            },
            "required": [
              "status",
              "attached_at",
              "source"
            ]
          },
          "anchor": {
            "type": [
              "object",
              "null"
            ],
            "title": "Daily anchor",
            "additionalProperties": true,
            "description": "The daily anchor covering this file, or null when none does yet — the ordinary answer\nfor a document sealed today, since anchors are published for a UTC day once it is\nover.\n\nEach day Signclad publishes a Merkle root over every artifact digest and audit chain\nhead sealed in that day, signs it with the document-signing key and timestamps it. A\nmatch here means this file's SHA-256, or its document's chain head, is a leaf of that\ntree: the digest existed by the anchor's date, independently of this API. The audit\npage inside every canonical PDF names the same anchor.\n",
            "properties": {
              "anchored_on": {
                "type": "string",
                "format": "date"
              },
              "root_sha256": {
                "type": "string",
                "description": "The Merkle root the leaves reduce to."
              },
              "timestamped": {
                "type": "boolean",
                "description": "Whether the anchor's root itself carries an RFC 3161 token yet."
              }
            },
            "required": [
              "anchored_on",
              "root_sha256",
              "timestamped"
            ]
          }
        },
        "required": [
          "document_id",
          "artifact",
          "sealed_at",
          "timestamp_valid",
          "chain_head",
          "timestamp",
          "anchor"
        ]
      },
      "VerifyResponse": {
        "type": "object",
        "title": "Verification result",
        "description": "Every artifact the submitted hash matches. Empty when it matches nothing.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "matches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VerifyMatch"
            }
          }
        },
        "required": [
          "matches"
        ]
      },
      "EmailSuppression": {
        "type": "object",
        "title": "Email suppression",
        "description": "One address Signclad will not mail, with the report that caused it. `released_at` is set\nwhen somebody lifted it; the row is never deleted.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "reason": {
            "type": "string",
            "enum": [
              "hard_bounce",
              "complaint",
              "manual"
            ]
          },
          "details": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the provider said."
          },
          "provider": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "postmark",
              "ses",
              "twilio"
            ]
          },
          "document_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The document whose delivery produced the report, when there was one."
          },
          "suppressed_at": {
            "type": "string",
            "format": "date-time"
          },
          "released_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "email",
          "reason",
          "suppressed_at"
        ]
      },
      "EmailSuppressionList": {
        "title": "Suppression list",
        "description": "A cursor-paginated page of suppressions.",
        "x-signclad-extension": true,
        "allOf": [
          {
            "$ref": "#/components/schemas/CursorPage"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/EmailSuppression"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "OkResponse": {
        "type": "object",
        "title": "Ok",
        "description": "The bare acknowledgement the session routes answer with.",
        "additionalProperties": true,
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ]
      },
      "NewPassword": {
        "type": "string",
        "title": "New password",
        "description": "At least 12 characters and at most 72 bytes, checked against a breach corpus. A long\npassword that appears in a known breach is refused with a `422`, so length alone is not a\nguarantee the value will be accepted.\n",
        "minLength": 12,
        "maxLength": 255
      },
      "RegisterRequest": {
        "type": "object",
        "title": "Register",
        "description": "The person, and the first workspace their account gets.",
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 255
          },
          "workspace_name": {
            "type": "string",
            "maxLength": 160
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 255,
            "description": "Lower-cased and trimmed before it is stored."
          },
          "password": {
            "$ref": "#/components/schemas/NewPassword"
          },
          "locale": {
            "type": "string",
            "maxLength": 8,
            "default": "en"
          },
          "timezone": {
            "type": "string",
            "maxLength": 64,
            "default": "UTC"
          }
        },
        "required": [
          "name",
          "workspace_name",
          "email",
          "password"
        ]
      },
      "LoginRequest": {
        "type": "object",
        "title": "Sign in",
        "additionalProperties": true,
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "password": {
            "type": "string"
          },
          "remember": {
            "type": "boolean"
          }
        },
        "required": [
          "email",
          "password"
        ]
      },
      "MfaPolicy": {
        "type": "object",
        "title": "MFA policy",
        "description": "Whether a second factor is required of this person, whether they have one, and how long\nthey have left to enrol.\n",
        "additionalProperties": true,
        "properties": {
          "required": {
            "type": "boolean"
          },
          "enrolled": {
            "type": "boolean"
          },
          "methods": {
            "type": "array",
            "description": "Which factors are available to them, in offer order.",
            "items": {
              "type": "string",
              "enum": [
                "passkey",
                "totp",
                "email",
                "recovery"
              ]
            }
          },
          "enforced_from": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "days_remaining": {
            "type": [
              "integer",
              "null"
            ],
            "description": "`null` when there is no deadline, or when they have already enrolled."
          },
          "blocked": {
            "type": "boolean",
            "description": "True when the deadline has passed and nothing is enrolled."
          }
        },
        "required": [
          "required",
          "enrolled"
        ]
      },
      "MfaStatus": {
        "title": "MFA status",
        "description": "The policy plus what this person actually has.",
        "allOf": [
          {
            "$ref": "#/components/schemas/MfaPolicy"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "recovery_codes_remaining": {
                "type": "integer"
              },
              "passkeys": {
                "type": "integer"
              },
              "totp": {
                "type": "boolean"
              },
              "email": {
                "type": "boolean",
                "description": "Whether a code sent to their own address is enrolled as a factor."
              }
            }
          }
        ]
      },
      "MfaStatusWithPasskeys": {
        "title": "MFA status with passkeys",
        "description": "What `GET /security/mfa` answers.",
        "allOf": [
          {
            "$ref": "#/components/schemas/MfaStatus"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "passkey_list": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Passkey"
                }
              },
              "totp_pending": {
                "type": "boolean",
                "description": "A setup was started and never confirmed."
              }
            }
          }
        ]
      },
      "SessionPayload": {
        "type": "object",
        "title": "Session",
        "description": "Who is signed in, and what their MFA position is.",
        "additionalProperties": true,
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "user": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              },
              "email_verified": {
                "type": "boolean"
              }
            }
          },
          "mfa": {
            "$ref": "#/components/schemas/MfaPolicy"
          }
        },
        "required": [
          "ok",
          "user"
        ]
      },
      "SessionPayloadWithRecovery": {
        "title": "Session after a challenge",
        "description": "The session payload, plus how many recovery codes are left.",
        "allOf": [
          {
            "$ref": "#/components/schemas/SessionPayload"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "recovery_codes_remaining": {
                "type": "integer"
              }
            }
          }
        ]
      },
      "MagicLinkSession": {
        "type": "object",
        "title": "Session from a link",
        "description": "What a consumed magic link answers. It carries no `mfa` block, and `email_verified` is\nalways `true` — following the link is itself proof of the address.\n",
        "additionalProperties": true,
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "user": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              },
              "email_verified": {
                "type": "boolean",
                "const": true
              }
            }
          }
        },
        "required": [
          "ok",
          "user"
        ]
      },
      "MfaChallenge": {
        "type": "object",
        "title": "MFA challenge",
        "description": "A second factor is needed. Answer it at `POST /login/mfa`. `passkey_options` is present\nonly when `passkey` is among the methods, and is the WebAuthn request options to hand to\n`navigator.credentials.get()`.\n",
        "additionalProperties": true,
        "properties": {
          "mfa_required": {
            "type": "boolean",
            "const": true
          },
          "methods": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "passkey",
                "totp",
                "recovery"
              ]
            }
          },
          "passkey_options": {
            "$ref": "#/components/schemas/WebAuthnOptions"
          }
        },
        "required": [
          "mfa_required",
          "methods"
        ]
      },
      "SsoRequired": {
        "type": "object",
        "title": "SSO required",
        "description": "What `POST /login` answers when the address belongs to a workspace that enforces single\nsign-on. A `200`, because nothing went wrong: the person simply signs in somewhere else.\n",
        "additionalProperties": true,
        "properties": {
          "sso_required": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Where to send them. `null` only if the connection became unusable between the\nenforcement check and this response, in which case show the message alone.\n"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "sso_required"
        ]
      },
      "MfaChallengeRequest": {
        "type": "object",
        "title": "Answer a challenge",
        "description": "`code` for `totp`, `email` and `recovery`; `credential` for `passkey`. The pending\nchallenge is held in the session, so nothing here identifies it.\n\nFor `email`, ask for the code with `POST /login/mfa/email` first.\n",
        "additionalProperties": true,
        "properties": {
          "method": {
            "type": "string",
            "enum": [
              "totp",
              "passkey",
              "email",
              "recovery"
            ]
          },
          "code": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 64
          },
          "credential": {
            "$ref": "#/components/schemas/WebAuthnCredential"
          }
        },
        "required": [
          "method"
        ]
      },
      "WebAuthnOptions": {
        "type": "object",
        "title": "WebAuthn options",
        "description": "A WebAuthn options object, passed through from the WebAuthn library with null members\nomitted. The key set follows that library rather than this document, so treat it as opaque\nand hand it to the browser unchanged.\n",
        "additionalProperties": true
      },
      "WebAuthnCredential": {
        "type": "object",
        "title": "WebAuthn credential",
        "description": "The credential the browser returned, forwarded verbatim.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "rawId": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "response": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "required": [
          "id",
          "rawId",
          "type",
          "response"
        ]
      },
      "PasskeyRegistrationRequest": {
        "type": "object",
        "title": "Register a passkey",
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 60
          },
          "credential": {
            "$ref": "#/components/schemas/WebAuthnCredential"
          }
        },
        "required": [
          "name",
          "credential"
        ]
      },
      "Passkey": {
        "type": "object",
        "title": "Passkey",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "transports": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "discoverable": {
            "type": "boolean"
          },
          "backed_up": {
            "type": "boolean",
            "description": "Whether the authenticator syncs the key, so losing one device does not lose it."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "TotpSetup": {
        "type": "object",
        "title": "Authenticator setup",
        "description": "The same secret in three forms, plus the parameters an authenticator needs. `qr_svg` is\ninline SVG markup, not a URL.\n",
        "additionalProperties": true,
        "properties": {
          "secret": {
            "type": "string",
            "description": "Base32, for manual entry."
          },
          "otpauth_uri": {
            "type": "string"
          },
          "qr_svg": {
            "type": "string"
          },
          "digits": {
            "type": "integer"
          },
          "period": {
            "type": "integer",
            "description": "Seconds per code."
          },
          "algorithm": {
            "type": "string"
          }
        },
        "required": [
          "secret",
          "otpauth_uri"
        ]
      },
      "UserSession": {
        "type": "object",
        "title": "Session",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "device": {
            "type": "string"
          },
          "user_agent": {
            "type": [
              "string",
              "null"
            ]
          },
          "ip_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "location": {
            "type": [
              "string",
              "null"
            ],
            "description": "Coarse, from the IP. City level at best."
          },
          "remembered": {
            "type": "boolean"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_active_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "current": {
            "type": "boolean"
          }
        },
        "required": [
          "id"
        ]
      },
      "SecurityEventType": {
        "type": "string",
        "title": "Security event type",
        "description": "Everything the security log records.",
        "enum": [
          "registered",
          "email_verified",
          "login_succeeded",
          "login_failed",
          "lockout_started",
          "logged_out",
          "logged_out_everywhere",
          "magic_link_requested",
          "magic_link_consumed",
          "google_linked",
          "google_unlinked",
          "google_login",
          "mfa_totp_enrolled",
          "mfa_totp_removed",
          "mfa_challenge_failed",
          "mfa_challenge_succeeded",
          "passkey_added",
          "passkey_renamed",
          "passkey_removed",
          "recovery_codes_generated",
          "recovery_code_used",
          "password_changed",
          "password_reset_requested",
          "password_reset_completed",
          "session_revoked",
          "sessions_revoked_others",
          "session_expired",
          "email_changed",
          "new_device_signin",
          "new_location_signin",
          "mfa_enrollment_blocked"
        ]
      },
      "SecurityEvent": {
        "type": "object",
        "title": "Security event",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/SecurityEventType"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "ip_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "user_agent": {
            "type": [
              "string",
              "null"
            ]
          },
          "location": {
            "type": [
              "string",
              "null"
            ]
          },
          "device": {
            "type": "string"
          },
          "context": {
            "type": [
              "object",
              "null"
            ],
            "description": "Event-specific detail. The keys depend on `type` — a revoked session carries the session\nid, a renamed passkey carries the name — so treat it as free-form.\n",
            "additionalProperties": true
          }
        },
        "required": [
          "id",
          "type",
          "created_at"
        ]
      },
      "WorkspaceListItem": {
        "type": "object",
        "title": "Workspace list item",
        "description": "The workspace object `GET /workspaces` returns. `test_mode` is always `false` here for a\nperson, whatever their keys would report, and `created_at` is never included — fetch the\nworkspace itself for that.\n\n`default_language`, `idle_timeout_minutes`, and `current` are\npresent for a signed-in person and **absent** for an API key, which sees only its own\nworkspace and the seven keys above them.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "member",
              "viewer"
            ]
          },
          "account_id": {
            "type": "string"
          },
          "test_mode": {
            "type": "boolean"
          },
          "default_language": {
            "type": "string"
          },
          "idle_timeout_minutes": {
            "type": "integer"
          },
          "current": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "Workspace": {
        "type": "object",
        "title": "Workspace",
        "description": "The full workspace object every other workspace operation returns.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "description": "The caller's role in it. Empty for a caller with no membership; `admin` for an API key."
          },
          "account_id": {
            "type": "string"
          },
          "test_mode": {
            "type": "boolean"
          },
          "default_language": {
            "type": "string"
          },
          "idle_timeout_minutes": {
            "type": "integer",
            "description": "How long a session may sit idle before it is signed out."
          },
          "current": {
            "type": "boolean",
            "description": "Whether this is the workspace the caller's session is pointed at."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Present on `GET /workspaces/{id}` and on writes; the list omits it."
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "MemberUser": {
        "type": "object",
        "title": "Member's user",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          }
        }
      },
      "Member": {
        "type": "object",
        "title": "Membership",
        "description": "The object the role, archive, and restore operations return. The list adds `seat` and\n`created_at`.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "member",
              "viewer"
            ]
          },
          "archived": {
            "type": "boolean"
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "user": {
            "$ref": "#/components/schemas/MemberUser"
          }
        },
        "required": [
          "id",
          "role"
        ]
      },
      "MemberListItem": {
        "title": "Membership list item",
        "description": "A membership as the list returns it.",
        "allOf": [
          {
            "$ref": "#/components/schemas/Member"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "seat": {
                "type": "boolean",
                "description": "Whether this membership takes a billed seat."
              },
              "created_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          }
        ]
      },
      "Invitation": {
        "type": "object",
        "title": "Invitation",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "member",
              "viewer"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "accepted",
              "revoked",
              "expired"
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "email",
          "role",
          "status"
        ]
      },
      "InvitationWithToken": {
        "title": "Invitation with its token",
        "description": "What `POST /members/invitations` returns. The token and accept URL appear here and nowhere\nelse; the same values go out in the email.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/Invitation"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "accept_url": {
                "type": "string",
                "format": "uri"
              },
              "token": {
                "type": "string"
              },
              "adds_seat": {
                "type": "boolean",
                "description": "Whether accepting will bill another seat."
              }
            },
            "required": [
              "token",
              "accept_url"
            ]
          }
        ]
      },
      "ApiKey": {
        "type": "object",
        "title": "API key",
        "description": "A key as it can be read back. `status` is derived, in this precedence: `revoked`, `expired`,\n`rotating`, `active`. `mode` and `sandbox` say the same thing twice.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "sandbox",
              "live"
            ]
          },
          "sandbox": {
            "type": "boolean"
          },
          "display_prefix": {
            "type": "string"
          },
          "last_four": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "rotating",
              "expired",
              "revoked"
            ]
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "rotated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "replaced_by_api_key_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "api_application_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "name",
          "status"
        ]
      },
      "ApiKeyWithSecret": {
        "title": "API key with its secret",
        "description": "The key object plus `key`, the plaintext. The property is **absent**, not null, on every\nother response — it exists only on a create and on the new half of a rotation.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/ApiKey"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "key": {
                "type": "string"
              }
            },
            "required": [
              "key"
            ]
          }
        ]
      },
      "RotatedApiKey": {
        "title": "Outgoing API key",
        "description": "The key being replaced, with the moment it stops working.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ApiKey"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "stops_working_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        ]
      },
      "PlanChangeRequest": {
        "type": "object",
        "title": "Plan change",
        "description": "`free` passes validation everywhere but is refused by `checkout_session` — there is nothing\nto buy.\n",
        "additionalProperties": true,
        "properties": {
          "plan": {
            "type": "string",
            "enum": [
              "free",
              "light",
              "business",
              "enterprise"
            ]
          },
          "interval": {
            "type": "string",
            "enum": [
              "yearly"
            ],
            "default": "yearly",
            "description": "All paid plans are billed annually."
          }
        },
        "required": [
          "plan"
        ]
      },
      "StripeSession": {
        "type": "object",
        "title": "Hosted session",
        "description": "A Stripe-hosted page to send the owner to.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          }
        },
        "required": [
          "id"
        ]
      },
      "BillingSummary": {
        "type": "object",
        "title": "Billing summary",
        "description": "Everything the billing page needs in one call. `GET /billing`, `PATCH /billing/plan`, and\n`POST /billing/cancel` all return this.\n",
        "additionalProperties": true,
        "properties": {
          "account_id": {
            "type": "string"
          },
          "plan": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "id": {
                "type": "string",
                "enum": [
                  "free",
                  "light",
                  "business",
                  "enterprise"
                ]
              },
              "interval": {
                "type": "string",
                "enum": [
                  "monthly",
                  "yearly"
                ]
              },
              "status": {
                "type": "string"
              },
              "cancels_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "Not a stored field: it is `current_period_end`, revealed only once a cancellation is\nscheduled. It can be `null` while `current_period_end` is set.\n"
              },
              "current_period_start": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "current_period_end": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "read_only": {
                "type": "boolean",
                "description": "True while billing is behind; reads keep working, writes answer `422 account_read_only`."
              },
              "grace_ends_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "payment_failed_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "seats": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "used": {
                "type": "integer"
              },
              "included": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "`null` means unlimited."
              },
              "billed": {
                "type": "integer"
              },
              "members": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "user_id": {
                      "type": "string"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "email": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "email"
                    },
                    "roles": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "workspaces": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "usage": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "period": {
                "type": "string",
                "examples": [
                  "2026-09"
                ]
              },
              "documents": {
                "type": "integer"
              },
              "documents_limit": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "api_documents": {
                "type": "integer"
              },
              "api_documents_included": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "api_documents_billable": {
                "type": "integer"
              }
            }
          },
          "card": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "on_file": {
                "type": "boolean"
              },
              "brand": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "last4": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "exp_month": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "exp_year": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            }
          },
          "invoices": {
            "type": "array",
            "description": "Empty when the account has no Stripe customer, and also when Stripe is not configured in\nthis environment — the two are indistinguishable.\n",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string"
                },
                "number": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "status": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "total": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Cents."
                },
                "currency": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "created_at": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "hosted_invoice_url": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uri"
                },
                "invoice_pdf": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uri"
                }
              }
            }
          },
          "features": {
            "type": "object",
            "description": "The plan's feature matrix, read from the database rather than from code, so the key set\nis data and not contract. Values are a boolean for a switch, an integer for a cap, or\n`null` for unlimited.\n\nThe seeded keys are `documents_per_month`, `api_documents_included`,\n`senders_included`, `templates`, `templates_per_sender`,\n`template_links`, `bulk_send`, `custom_branding`, `conditional_rules`,\n`data_validation`, `attachment_requests`, `in_person`, `sso`, `baa`,\n`baa_eligible`, `api_access`, `sandbox_keys`, `webhooks`, `audit_trail_json`,\n`cfr_part11`, `nom151`, `id_verification`, `pades_lt`, and `eu_residency`. A feature\nmissing from a plan's row reads as off.\n",
            "additionalProperties": {
              "type": [
                "boolean",
                "integer",
                "null"
              ]
            }
          }
        },
        "required": [
          "account_id"
        ]
      },
      "AuditLogEntry": {
        "type": "object",
        "title": "Audit log entry",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "action": {
            "type": "string",
            "examples": [
              "workspace.updated",
              "member.archived",
              "api_key.rotated"
            ]
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "actor": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "user",
                  "api_key",
                  "system",
                  "stripe"
                ]
              },
              "label": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "user_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "api_key_id": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "target": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "type": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "context": {
            "type": "object",
            "description": "Action-specific detail — a before/after pair on an update, the transferred templates on\nan archive. Free-form, and serialized as an empty array rather than an empty object when\nthere is nothing in it.\n",
            "additionalProperties": true
          },
          "ip": {
            "type": [
              "string",
              "null"
            ]
          },
          "user_agent": {
            "type": [
              "string",
              "null"
            ]
          },
          "request_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "action",
          "created_at"
        ]
      },
      "OAuthScope": {
        "type": "string",
        "title": "OAuth scope",
        "description": "A write scope satisfies the matching read scope, so `documents:write` alone is enough to\nread documents. `offline_access` is what makes a refresh token possible; it is never\ngranted to a client credentials token.\n",
        "enum": [
          "documents:read",
          "documents:write",
          "templates:read",
          "templates:write",
          "webhooks:read",
          "webhooks:write",
          "account:read",
          "offline_access"
        ]
      },
      "OAuthErrorBody": {
        "type": "object",
        "title": "OAuth error",
        "description": "The OAuth envelope, and the one place on this API that does not use `message`/`meta` — the\nRFCs define it, so it wins.\n",
        "additionalProperties": true,
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "invalid_request",
              "invalid_client",
              "invalid_grant",
              "unauthorized_client",
              "unsupported_grant_type",
              "unsupported_response_type",
              "invalid_scope",
              "invalid_target",
              "invalid_token",
              "access_denied",
              "login_required",
              "invalid_client_metadata",
              "invalid_redirect_uri"
            ]
          },
          "error_description": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "externalDocs": {
          "description": "`unsupported_response_type`, `access_denied` and `login_required` are delivered as\n**redirect parameters** on the client's `redirect_uri`, with `state` echoed, rather than\nas a JSON body — the RFCs require the redirect wherever one can be trusted. `error_uri`\nis never set.\n",
          "url": "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1"
        }
      },
      "AuthorizationServerMetadata": {
        "type": "object",
        "title": "Authorization server metadata",
        "additionalProperties": true,
        "properties": {
          "issuer": {
            "type": "string",
            "format": "uri"
          },
          "authorization_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "token_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "registration_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "revocation_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OAuthScope"
            }
          },
          "response_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "response_modes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "grant_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "code_challenge_methods_supported": {
            "type": "array",
            "description": "`S256` only. Plain PKCE is not accepted, and neither is no PKCE.",
            "items": {
              "type": "string"
            }
          },
          "prompt_values_supported": {
            "type": "array",
            "description": "The `prompt` values the authorization endpoint acts on. Advertised so a client can\nattempt a silent authorization without finding out by trying.\n",
            "items": {
              "type": "string",
              "enum": [
                "none",
                "login",
                "consent"
              ]
            }
          },
          "token_endpoint_auth_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "revocation_endpoint_auth_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "resource_indicators_supported": {
            "type": "boolean"
          },
          "authorization_response_iss_parameter_supported": {
            "type": "boolean"
          },
          "service_documentation": {
            "type": "string",
            "format": "uri"
          },
          "ui_locales_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "issuer",
          "authorization_endpoint",
          "token_endpoint"
        ]
      },
      "ProtectedResourceMetadata": {
        "type": "object",
        "title": "Protected resource metadata",
        "additionalProperties": true,
        "properties": {
          "resource": {
            "type": "string",
            "format": "uri"
          },
          "authorization_servers": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OAuthScope"
            }
          },
          "bearer_methods_supported": {
            "type": "array",
            "description": "`header` only — no query parameter, no form field.",
            "items": {
              "type": "string"
            }
          },
          "resource_documentation": {
            "type": "string",
            "format": "uri"
          },
          "signclad_introspection_cache_seconds": {
            "type": "integer",
            "description": "How long a resource server may cache an introspection result. Signclad's own guard\ncaches nothing; this is the ceiling it asks others to respect, and the reason a\ndisconnection is visible within a minute.\n",
            "x-signclad-extension": true
          }
        },
        "required": [
          "resource",
          "authorization_servers"
        ]
      },
      "ConsentPayload": {
        "type": "object",
        "title": "Consent screen",
        "description": "Everything a SPA needs to render its own consent screen, including the exact form fields to\npost back.\n",
        "additionalProperties": true,
        "properties": {
          "client": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "client_id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "client_uri": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "logo_uri": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "policy_uri": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "tos_uri": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "verified": {
                "type": "boolean",
                "description": "False for a dynamically registered client — anybody can register one."
              },
              "registered_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "scopes": {
            "type": "array",
            "description": "The scopes being asked for, in plain language.",
            "items": {
              "$ref": "#/components/schemas/ScopeDescription"
            }
          },
          "workspaces": {
            "type": "array",
            "description": "Which workspace the grant can be scoped to. One is chosen, not all.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "role": {
                  "type": "string"
                },
                "current": {
                  "type": "boolean"
                }
              }
            }
          },
          "resource": {
            "type": "string",
            "format": "uri"
          },
          "user": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              }
            }
          },
          "form_action": {
            "type": "string",
            "format": "uri"
          },
          "form_fields": {
            "type": "object",
            "description": "Post these back verbatim, plus `action` and `workspace_id`.",
            "additionalProperties": {
              "type": "string"
            }
          }
        },
        "required": [
          "client",
          "scopes"
        ]
      },
      "ScopeDescription": {
        "type": "object",
        "title": "Scope description",
        "additionalProperties": true,
        "properties": {
          "scope": {
            "$ref": "#/components/schemas/OAuthScope"
          },
          "title": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          }
        },
        "required": [
          "scope"
        ]
      },
      "AuthorizationDecision": {
        "type": "object",
        "title": "Consent decision",
        "description": "Every authorization parameter again — they are re-validated rather than trusted from the\nsession — plus the decision and the workspace it applies to.\n",
        "additionalProperties": true,
        "properties": {
          "action": {
            "type": "string",
            "description": "`approve` approves. Anything else, including absent, declines."
          },
          "workspace_id": {
            "type": "string",
            "description": "Required to approve. Must be a live membership of the signed-in person."
          },
          "client_id": {
            "type": "string"
          },
          "redirect_uri": {
            "type": "string",
            "format": "uri"
          },
          "response_type": {
            "type": "string",
            "enum": [
              "code"
            ]
          },
          "scope": {
            "type": "string"
          },
          "code_challenge": {
            "type": "string"
          },
          "code_challenge_method": {
            "type": "string",
            "enum": [
              "S256"
            ]
          },
          "state": {
            "type": "string"
          },
          "nonce": {
            "type": "string"
          },
          "resource": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "client_id",
          "response_type",
          "code_challenge",
          "code_challenge_method"
        ]
      },
      "TokenRequest": {
        "type": "object",
        "title": "Token request",
        "description": "Form-encoded. Which fields matter depends on `grant_type`: `code` and `code_verifier` for\n`authorization_code`, `refresh_token` for a refresh, neither for `client_credentials`.\n",
        "additionalProperties": true,
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "authorization_code",
              "refresh_token",
              "client_credentials"
            ]
          },
          "code": {
            "type": "string"
          },
          "code_verifier": {
            "type": "string"
          },
          "redirect_uri": {
            "type": "string",
            "format": "uri",
            "description": "Required unless the client registered exactly one. Must match the code's."
          },
          "refresh_token": {
            "type": "string"
          },
          "scope": {
            "type": "string",
            "description": "On a refresh, may only narrow. On client credentials, defaults to the client's scopes."
          },
          "resource": {
            "type": "string",
            "format": "uri",
            "description": "Must match the audience the grant was issued for; a refresh cannot change it."
          },
          "client_id": {
            "type": "string"
          },
          "client_secret": {
            "type": "string"
          }
        },
        "required": [
          "grant_type"
        ]
      },
      "TokenResponse": {
        "type": "object",
        "title": "Token",
        "additionalProperties": true,
        "properties": {
          "access_token": {
            "type": "string"
          },
          "token_type": {
            "type": "string",
            "const": "Bearer"
          },
          "expires_in": {
            "type": "integer"
          },
          "scope": {
            "type": "string",
            "description": "Space-delimited, always present."
          },
          "refresh_token": {
            "type": "string",
            "description": "Present only when `offline_access` was granted. Rotates on every use: replaying one\nrevokes the whole family.\n"
          },
          "nonce": {
            "type": "string",
            "description": "Present only when the authorization request carried one."
          }
        },
        "required": [
          "access_token",
          "token_type",
          "expires_in",
          "scope"
        ]
      },
      "ClientRegistrationRequest": {
        "type": "object",
        "title": "Client registration",
        "description": "RFC 7591 metadata. Unknown members are ignored; a `client_uri`, `logo_uri`, `policy_uri`, or\n`tos_uri` that is not http(s) is silently dropped rather than rejected.\n",
        "additionalProperties": true,
        "properties": {
          "redirect_uris": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "description": "HTTPS with a host, a loopback address (`127.0.0.1`, `::1`, `localhost`), or a\nreverse-domain private-use scheme such as `com.example.app:/callback`. No fragments.\n",
            "items": {
              "type": "string"
            }
          },
          "client_name": {
            "type": "string",
            "maxLength": 120
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "enum": [
              "none"
            ],
            "description": "Dynamic registration issues public clients only."
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "authorization_code",
                "refresh_token"
              ]
            }
          },
          "response_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "code"
              ]
            }
          },
          "scope": {
            "type": "string",
            "description": "Space-delimited."
          },
          "contacts": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "client_uri": {
            "type": "string"
          },
          "logo_uri": {
            "type": "string"
          },
          "policy_uri": {
            "type": "string"
          },
          "tos_uri": {
            "type": "string"
          },
          "software_id": {
            "type": "string",
            "maxLength": 120
          },
          "software_version": {
            "type": "string",
            "maxLength": 120
          },
          "software_statement": {
            "type": "string",
            "description": "Stored as sent. Not verified."
          }
        },
        "required": [
          "redirect_uris"
        ]
      },
      "ClientRegistration": {
        "type": "object",
        "title": "Registered client",
        "description": "Optional members are **absent** rather than null when they were not set, as RFC 7591\nexpects.\n",
        "additionalProperties": true,
        "properties": {
          "client_id": {
            "type": "string"
          },
          "client_id_issued_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Unix seconds — the one timestamp on this API that is not an ISO string."
          },
          "client_name": {
            "type": "string"
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "response_types": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "token_endpoint_auth_method": {
            "type": "string"
          },
          "scope": {
            "type": "string"
          },
          "client_uri": {
            "type": "string"
          },
          "logo_uri": {
            "type": "string"
          },
          "policy_uri": {
            "type": "string"
          },
          "tos_uri": {
            "type": "string"
          },
          "software_id": {
            "type": "string"
          },
          "software_version": {
            "type": "string"
          },
          "contacts": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "client_id",
          "client_name",
          "redirect_uris"
        ]
      },
      "ClientRegistrationWithToken": {
        "title": "Registered client with its management token",
        "description": "The registration, plus the two members that exist only on the create.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ClientRegistration"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "registration_access_token": {
                "type": "string"
              },
              "registration_client_uri": {
                "type": "string",
                "format": "uri"
              }
            },
            "required": [
              "registration_access_token",
              "registration_client_uri"
            ]
          }
        ]
      },
      "OAuthAuthorization": {
        "type": "object",
        "title": "Connected application",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "application": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "client_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "client_uri": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "logo_uri": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "self_registered": {
                "type": "boolean"
              }
            }
          },
          "user": {
            "description": "`null` for a client credentials grant, which nobody personally approved.",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "grant_type": {
            "type": "string",
            "enum": [
              "authorization_code",
              "client_credentials"
            ]
          },
          "resource": {
            "type": "string",
            "format": "uri"
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OAuthScope"
            }
          },
          "permissions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScopeDescription"
            }
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id"
        ]
      },
      "OAuthClient": {
        "type": "object",
        "title": "Server-to-server client",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "client_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "confidential"
            ]
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OAuthScope"
            }
          },
          "permissions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScopeDescription"
            }
          },
          "secret_last_four": {
            "type": [
              "string",
              "null"
            ]
          },
          "token_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "client_id",
          "name"
        ]
      },
      "OAuthClientWithSecret": {
        "title": "Client with its secret",
        "description": "The secret is shown once, on creation, and cannot be read back.",
        "allOf": [
          {
            "$ref": "#/components/schemas/OAuthClient"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "client_secret": {
                "type": "string"
              }
            },
            "required": [
              "client_secret"
            ]
          }
        ]
      },
      "LogoUpload": {
        "type": "object",
        "title": "Logo upload",
        "description": "PNG, JPEG, WebP, or GIF, at most 2 MB. **SVG is refused**: it can carry script, and this\nimage renders inside other people's pages.\n",
        "additionalProperties": true,
        "properties": {
          "logo": {
            "type": "string",
            "format": "binary"
          }
        },
        "required": [
          "logo"
        ]
      },
      "ApiApplicationWrite": {
        "type": "object",
        "title": "API application settings",
        "description": "`name` is required on create and optional on update; everything else is optional in both. A\nkey present and `null` clears it.\n",
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 255
          },
          "callback_urls": {
            "type": "array",
            "maxItems": 50,
            "description": "Origins — scheme and host only, no path. Normalized on write.",
            "items": {
              "type": "string",
              "maxLength": 2048
            }
          },
          "allowed_embed_domains": {
            "type": "array",
            "maxItems": 50,
            "description": "Origins allowed to frame this application's embedded pages, enforced through\n`frame-ancestors`. Left out of a request that sets `callback_urls`, it follows them.\n",
            "items": {
              "type": "string",
              "maxLength": 2048
            }
          },
          "primary_color": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
          },
          "button_text_color": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
          },
          "link_text_color": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
          },
          "buttons_border_radius": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 64
          },
          "default_webhook_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Must name a webhook endpoint in this workspace."
          }
        }
      },
      "BrandingStyles": {
        "type": "object",
        "title": "Resolved branding",
        "description": "What a page or an email will actually use, after the API application, the workspace, and the\nbuilt-in defaults have been resolved in that order. Nothing here is null.\n",
        "additionalProperties": true,
        "properties": {
          "primary_color": {
            "type": "string"
          },
          "button_text_color": {
            "type": "string"
          },
          "link_text_color": {
            "type": "string"
          },
          "buttons_border_radius": {
            "type": "integer"
          },
          "logo_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "source": {
            "type": "string",
            "description": "Which layer won.",
            "enum": [
              "api_application",
              "workspace",
              "default"
            ]
          }
        }
      },
      "SendingDefaults": {
        "type": "object",
        "title": "Sending defaults",
        "description": "What a create body inherits when it does not say. All seven keys are always present on the\nway out, and `null` means \"no workspace default, use the built-in one\".\n\nOn the way **in**, through `PATCH /workspace/branding`, only `expires_in`,\n`redirect_url`, `decline_redirect_url` and `language` accept `null` to clear them.\n`reminders`, `allow_decline` and `allow_reassign` are booleans on write: sending `null`\nis `422`. Omit the key instead to leave the default alone.\n",
        "additionalProperties": true,
        "properties": {
          "expires_in": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 365
          },
          "reminders": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "redirect_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "decline_redirect_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "allow_decline": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "allow_reassign": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "language": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Language"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "WorkspaceBranding": {
        "type": "object",
        "title": "Workspace branding",
        "additionalProperties": true,
        "properties": {
          "logo_file_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "logo_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Short-lived signed URL. Differs between reads."
          },
          "from_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The name invitations come from, instead of Signclad's."
          },
          "email_signature": {
            "type": [
              "string",
              "null"
            ],
            "description": "HTML, sanitized on write."
          },
          "primary_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "button_text_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "link_text_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "buttons_border_radius": {
            "type": [
              "integer",
              "null"
            ]
          },
          "available": {
            "type": "boolean",
            "description": "Whether the stored logo can actually be served. `false` when the file is missing or\nthe storage driver cannot sign a URL for it, in which case `logo_url` is `null` too.\n"
          },
          "sending_defaults": {
            "$ref": "#/components/schemas/SendingDefaults"
          },
          "resolved": {
            "$ref": "#/components/schemas/BrandingStyles"
          }
        }
      },
      "WorkspaceBrandingWrite": {
        "type": "object",
        "title": "Workspace branding settings",
        "description": "Only the keys present change; a key present and `null` clears it.",
        "additionalProperties": true,
        "properties": {
          "logo_file_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "from_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100
          },
          "email_signature": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 4000
          },
          "primary_color": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
          },
          "button_text_color": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
          },
          "link_text_color": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
          },
          "buttons_border_radius": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 64
          },
          "sending_defaults": {
            "$ref": "#/components/schemas/SendingDefaults"
          }
        }
      },
      "PublicIntake": {
        "type": "object",
        "title": "Public intake page",
        "description": "What a stranger holding a fill link may see. No template id, no workspace id, no field list,\nno other recipients.\n",
        "additionalProperties": true,
        "properties": {
          "template_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "role": {
            "type": "string",
            "description": "The placeholder this link fills, by name."
          },
          "subject": {
            "type": [
              "string",
              "null"
            ]
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          },
          "test_mode": {
            "type": "boolean"
          },
          "sender": {
            "type": "object",
            "description": "Whose form this is, and how to style the page. Always present.",
            "additionalProperties": true,
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "logo_url": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "primary_color": {
                "type": "string"
              },
              "button_text_color": {
                "type": "string"
              },
              "link_text_color": {
                "type": "string"
              },
              "buttons_border_radius": {
                "type": "integer"
              }
            }
          },
          "required_fields": {
            "type": "array",
            "description": "What this link insists on before it will start a document.",
            "items": {
              "type": "string",
              "enum": [
                "name",
                "email",
                "captcha_token"
              ]
            }
          },
          "require_name_email": {
            "type": "boolean"
          },
          "captcha_required": {
            "type": "boolean"
          },
          "captcha": {
            "type": [
              "object",
              "null"
            ],
            "description": "Whatever the configured captcha driver needs on the page — a Turnstile site key, for\ninstance. `null` when no captcha is required, and driver-shaped when one is, so treat it\nas opaque.\n",
            "additionalProperties": true
          },
          "single_use": {
            "type": "boolean"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "redirect_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          }
        },
        "required": [
          "role"
        ]
      },
      "PublicIntakeStart": {
        "type": "object",
        "title": "Started intake",
        "description": "The document that was just created, and a session to sign it with, so the page can continue\nstraight into signing. `status` is the literal `Draft`.\n",
        "additionalProperties": true,
        "properties": {
          "document_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "const": "Draft"
          },
          "redirect_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "session": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "document_id": {
                "type": "string"
              },
              "recipient_id": {
                "type": "string"
              },
              "embedded_signing_url": {
                "type": "string",
                "format": "uri"
              },
              "exchange_token": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Exchange it at `POST /signer/exchange` for a signer session."
              }
            }
          }
        },
        "required": [
          "document_id"
        ]
      },
      "TemplateLinkSettings": {
        "type": "object",
        "title": "Template link settings",
        "additionalProperties": true,
        "properties": {
          "enabled": {
            "type": "boolean",
            "default": false,
            "description": "A link exists as soon as the template does, switched off."
          },
          "require_name_email": {
            "type": "boolean",
            "default": true
          },
          "single_use": {
            "type": "boolean",
            "default": false
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "A time in the past is accepted, and makes the link unusable."
          },
          "redirect_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Must be HTTPS."
          },
          "notify_sender": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "TemplateLink": {
        "type": "object",
        "title": "Template link",
        "description": "The public URL, its settings, and its use so far. Note `uses` and `last_used_at` sit beside\n`link_settings` rather than inside it.\n",
        "additionalProperties": true,
        "properties": {
          "template_id": {
            "type": "string",
            "format": "uuid"
          },
          "template_link": {
            "type": "string",
            "format": "uri"
          },
          "link_settings": {
            "$ref": "#/components/schemas/TemplateLinkSettings"
          },
          "usable": {
            "type": "boolean",
            "description": "Enabled, unexpired, and — if single-use — not yet spent."
          },
          "uses": {
            "type": "integer"
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "template_id",
          "template_link"
        ]
      },
      "StaffReason": {
        "type": "string",
        "title": "Staff reason",
        "description": "Why a staff member did this. At least eight characters, and written to the staff audit log —\nan override nobody can explain later is not one worth having.\n",
        "minLength": 8,
        "maxLength": 500
      },
      "StaffReasonBody": {
        "type": "object",
        "title": "Staff reason",
        "additionalProperties": true,
        "properties": {
          "reason": {
            "$ref": "#/components/schemas/StaffReason"
          }
        },
        "required": [
          "reason"
        ]
      },
      "AdminLookupResult": {
        "type": "object",
        "title": "Lookup result",
        "additionalProperties": true,
        "properties": {
          "workspace_id": {
            "type": "string"
          },
          "workspace_name": {
            "type": "string"
          },
          "account_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "account_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "plan": {
            "type": [
              "string",
              "null"
            ]
          },
          "plan_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "matched_by": {
            "type": "string",
            "description": "Which identifier hit.",
            "enum": [
              "workspace_id",
              "account_id",
              "document_id",
              "api_key",
              "email",
              "name",
              "workspace"
            ]
          },
          "members": {
            "type": "integer"
          },
          "documents": {
            "type": "integer"
          },
          "suspended_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "workspace_id"
        ]
      },
      "AdminAccountOverview": {
        "type": "object",
        "title": "Account overview",
        "description": "What a support engineer needs on one screen. `account` and `reputation` are `null` when the\nworkspace has neither.\n",
        "additionalProperties": true,
        "properties": {
          "workspace": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "created_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "deleted_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "purge_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "suspended_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "suspended_reason": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "compat_enabled": {
                "type": "boolean"
              }
            }
          },
          "account": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "plan": {
                "type": "string"
              },
              "plan_interval": {
                "type": "string"
              },
              "plan_status": {
                "type": "string"
              },
              "owner_email": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "email"
              },
              "card_on_file": {
                "type": "boolean"
              },
              "enterprise": {
                "type": "boolean"
              },
              "legal_hold": {
                "type": "boolean"
              },
              "read_only": {
                "type": "boolean"
              },
              "trial_ends_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "grace_ends_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "read_only_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "suspended_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "current_period_end": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "stripe_customer_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "api_pricing": {
                "type": "object",
                "additionalProperties": true,
                "description": "The negotiated API pricing override, as `PATCH\n/admin/accounts/{account}/api-pricing` set it. All three are `null` on list\npricing.\n",
                "properties": {
                  "stripe_price_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "rate_usd": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "A per-document price in dollars, as a decimal string."
                  },
                  "effective_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                }
              },
              "workspaces": {
                "type": "integer"
              }
            }
          },
          "usage": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "period": {
                "type": "string"
              },
              "documents": {
                "type": "integer"
              },
              "api_documents": {
                "type": "integer"
              },
              "documents_limit": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            }
          },
          "counts": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "members": {
                "type": "integer"
              },
              "documents": {
                "type": "integer"
              },
              "blocked_documents": {
                "type": "integer"
              },
              "api_keys": {
                "type": "integer"
              },
              "open_abuse_reports": {
                "type": "integer"
              }
            }
          },
          "reputation": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "properties": {
              "score": {
                "type": "integer"
              },
              "documents_sent": {
                "type": "integer"
              },
              "bounces": {
                "type": "integer"
              },
              "complaints": {
                "type": "integer"
              },
              "reports": {
                "type": "integer"
              },
              "scan_warnings": {
                "type": "integer"
              },
              "computed_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          }
        },
        "required": [
          "workspace"
        ]
      },
      "AdminMember": {
        "type": "object",
        "title": "Member, staff view",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "user_id": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "role": {
            "type": "string"
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "mfa_enrolled": {
            "type": "boolean"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "AdminApiKey": {
        "type": "object",
        "title": "API key, staff view",
        "description": "Prefixes and usage only. No secret, no hash.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "display_prefix": {
            "type": "string"
          },
          "sandbox": {
            "type": "boolean"
          },
          "usable": {
            "type": "boolean"
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "AdminWebhook": {
        "type": "object",
        "title": "Webhook endpoint, staff view",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "status": {
            "type": "string"
          },
          "health": {
            "type": "string"
          },
          "consecutive_failures": {
            "type": "integer"
          },
          "last_delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_failure_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_failure_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "failing_since": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "test_mode": {
            "type": "boolean"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "AdminDocument": {
        "type": "object",
        "title": "Document, staff listing",
        "description": "Metadata only — no fields, no values, no files.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "description": "The internal state."
          },
          "status": {
            "$ref": "#/components/schemas/DocumentStatus"
          },
          "test_mode": {
            "type": "boolean"
          },
          "recipients": {
            "type": "integer"
          },
          "error_message": {
            "type": [
              "string",
              "null"
            ]
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "AdminDocumentState": {
        "type": "object",
        "title": "Document after a staff action",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "state": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/DocumentStatus"
          },
          "workspace_id": {
            "type": "string"
          },
          "error_message": {
            "type": [
              "string",
              "null"
            ]
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "state",
          "status"
        ]
      },
      "AdminAuditEntry": {
        "type": "object",
        "title": "Audit entry, staff view",
        "description": "The customer's audit entry, flattened.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "action": {
            "type": "string"
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "actor_type": {
            "type": "string"
          },
          "actor_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "target_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "target_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "context": {
            "type": "object",
            "additionalProperties": true
          },
          "ip": {
            "type": [
              "string",
              "null"
            ]
          },
          "request_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "action",
          "created_at"
        ]
      },
      "AdminRefund": {
        "type": "object",
        "title": "Refund",
        "description": "A refund attempt. `status: failed` with a `failure_message` is a recorded attempt, not a\nfailed request — the operation still answered `200`.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "amount_cents": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          },
          "target": {
            "type": [
              "string",
              "null"
            ],
            "description": "The Stripe invoice or charge it was issued against."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "succeeded",
              "failed"
            ]
          },
          "gateway_refund_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "failure_message": {
            "type": [
              "string",
              "null"
            ]
          },
          "staff_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "amount_cents",
          "status"
        ]
      },
      "Impersonation": {
        "type": "object",
        "title": "Impersonation grant",
        "additionalProperties": true,
        "properties": {
          "active": {
            "type": "boolean"
          },
          "id": {
            "type": "string"
          },
          "staff_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "user_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "ticket": {
            "type": [
              "string",
              "null"
            ]
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "active",
          "id"
        ]
      },
      "AbuseReport": {
        "type": "object",
        "title": "Abuse report",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "signer_report",
              "scorer",
              "staff"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "phishing",
              "spam",
              "not_me",
              "content",
              "other"
            ]
          },
          "detail": {
            "type": [
              "string",
              "null"
            ]
          },
          "score": {
            "type": [
              "integer",
              "null"
            ]
          },
          "signals": {
            "type": "object",
            "description": "What the scorer saw. Free-form.",
            "additionalProperties": true
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "released",
              "canceled",
              "banned",
              "dismissed"
            ]
          },
          "reporter_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "blocked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "blocked_from_state": {
            "type": [
              "string",
              "null"
            ],
            "description": "The state the document was in when it was held, so a release can restore it."
          },
          "reviewed_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "reviewed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "review_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "workspace": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "plan": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "created_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "suspended_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "document": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "state": {
                "type": "string"
              },
              "status": {
                "$ref": "#/components/schemas/DocumentStatus"
              },
              "recipients": {
                "type": "integer"
              },
              "sent_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "preview_url": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        },
        "required": [
          "id",
          "status"
        ]
      },
      "AbuseReportDetail": {
        "title": "Abuse report with its neighbours",
        "description": "The report, plus up to ten others against the same workspace — the pattern matters.",
        "allOf": [
          {
            "$ref": "#/components/schemas/AbuseReport"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "related_reports": {
                "type": "array",
                "maxItems": 10,
                "items": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "source": {
                      "type": "string"
                    },
                    "reason": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "score": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "IdVerificationRequest": {
        "type": "object",
        "title": "Identity verification requirement",
        "description": "Whether the signer must prove who they are with a government ID before signing, and through\nwhich provider. Set on a document for everybody, or on a single recipient. `provider` is\n**absent**, not null, when it was never named — the account default applies.\n",
        "additionalProperties": true,
        "properties": {
          "required": {
            "type": "boolean"
          },
          "provider": {
            "type": "string",
            "enum": [
              "stripe_identity",
              "persona"
            ],
            "description": "Which driver checks the ID. Validated when the document or template is written, so a\nname outside this set is `422` at create time rather than a surprise when a signer\nreaches the step. Omit it — the key absent, not null — to take the account default.\n\nA named driver that is in the set but has no configuration behind it in this\nenvironment answers `503 provider_not_configured` when a signer reaches it.\n"
          }
        },
        "required": [
          "required"
        ]
      },
      "ReauthenticationChallenge": {
        "type": "object",
        "title": "Re-authentication challenge",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "method": {
            "type": "string",
            "description": "`email_otp` sends a code; `password` asks for the signer's own Signclad password, which\nonly exists when the signer is a user.\n",
            "enum": [
              "email_otp",
              "password"
            ]
          },
          "cfr_part11_signing_mode": {
            "type": "string",
            "enum": [
              "streamlined",
              "per_signature"
            ]
          },
          "field_api_id": {
            "type": "string",
            "description": "Absent in `streamlined` mode."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "code": {
            "type": "string",
            "description": "Present **only** on a test-mode document, so an automated test can finish without\nreading email. Never present on a real document.\n"
          }
        },
        "required": [
          "id",
          "method",
          "cfr_part11_signing_mode",
          "expires_at"
        ]
      },
      "ReauthenticationResult": {
        "type": "object",
        "title": "Re-authentication result",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "verified_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "field_api_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "challenge_token": {
            "type": "string",
            "description": "Present this with the signature it authorizes."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "challenge_token",
          "expires_at"
        ]
      },
      "IdVerificationCheck": {
        "type": "object",
        "title": "Identity verification check",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "provider": {
            "type": "string",
            "examples": [
              "fake",
              "stripe_identity",
              "persona"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "requires_input",
              "processing",
              "verified",
              "failed",
              "canceled"
            ]
          },
          "verified": {
            "type": "boolean"
          },
          "verification_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Where to send the signer. Expires on the provider's schedule, not ours."
          },
          "name_match": {
            "type": [
              "string",
              "null"
            ],
            "description": "Whether the name on the document matches the name on the ID.",
            "enum": [
              "match",
              "mismatch",
              "unknown",
              null
            ]
          },
          "failure_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "status",
          "verified"
        ]
      },
      "SsoServiceProvider": {
        "type": "object",
        "title": "Service provider details",
        "description": "The values an IdP administrator pastes into their end. Always present, never null.",
        "additionalProperties": true,
        "properties": {
          "entity_id": {
            "type": "string"
          },
          "acs_url": {
            "type": "string",
            "format": "uri"
          },
          "slo_url": {
            "type": "string",
            "format": "uri"
          },
          "login_url": {
            "type": "string",
            "format": "uri"
          },
          "metadata_url": {
            "type": "string",
            "format": "uri"
          },
          "oidc_redirect_uri": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "SsoConfigUnconfigured": {
        "type": "object",
        "title": "SSO, not configured",
        "description": "What a workspace with no connection yet answers — just enough to start the setup.",
        "additionalProperties": true,
        "properties": {
          "configured": {
            "type": "boolean",
            "const": false
          },
          "service_provider": {
            "$ref": "#/components/schemas/SsoServiceProvider"
          }
        },
        "required": [
          "configured",
          "service_provider"
        ]
      },
      "SsoConfig": {
        "type": "object",
        "title": "SSO configuration",
        "additionalProperties": true,
        "properties": {
          "configured": {
            "type": "boolean",
            "const": true
          },
          "protocol": {
            "type": "string",
            "enum": [
              "saml",
              "oidc"
            ]
          },
          "enabled": {
            "type": "boolean"
          },
          "usable": {
            "type": "boolean",
            "description": "Whether the connection has everything it needs to actually sign somebody in."
          },
          "enforce": {
            "type": "boolean",
            "description": "Require SSO for the allowed domains — password sign-in stops working for them. Cannot be\nturned on while the connection is unusable or no domains are listed.\n"
          },
          "allowed_domains": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "saml": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "idp_entity_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "idp_sso_url": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "idp_slo_url": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "metadata_url": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "metadata_fetched_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "certificate_present": {
                "type": "boolean"
              },
              "certificate_expires_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "oidc": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "issuer": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "client_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "client_secret_set": {
                "type": "boolean",
                "description": "The secret itself is never returned."
              },
              "discovery_fetched_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "attribute_map": {
            "type": "object",
            "description": "Which assertion attributes carry the address, the name, and the groups.",
            "additionalProperties": true,
            "properties": {
              "email": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "groups": {
                "type": "string"
              }
            }
          },
          "provisioning": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "jit_enabled": {
                "type": "boolean",
                "description": "Create a membership the first time somebody signs in."
              },
              "default_role": {
                "type": "string",
                "enum": [
                  "admin",
                  "member",
                  "viewer"
                ]
              },
              "group_role_map": {
                "type": "object",
                "description": "IdP group name to Signclad role. `owner` cannot be provisioned.",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          },
          "scim": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "token_prefix": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "created_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "last_used_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "endpoint": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "last_login_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "service_provider": {
            "$ref": "#/components/schemas/SsoServiceProvider"
          }
        },
        "required": [
          "configured",
          "protocol"
        ]
      },
      "SsoConfigWrite": {
        "type": "object",
        "title": "SSO settings",
        "description": "Flat, unlike the response: `idp_*` and `oidc_*` come back nested under `saml` and `oidc`,\nand `jit_enabled`, `default_role`, and `group_role_map` under `provisioning`. A field left\nout is untouched; a field sent empty is cleared.\n",
        "additionalProperties": true,
        "properties": {
          "protocol": {
            "type": "string",
            "enum": [
              "saml",
              "oidc"
            ],
            "description": "Required on the first write."
          },
          "enabled": {
            "type": "boolean"
          },
          "enforce": {
            "type": "boolean"
          },
          "allowed_domains": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "string",
              "maxLength": 253
            }
          },
          "metadata_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "maxLength": 2048,
            "description": "Fetched server-side. Sending it forces `protocol` to `saml` and overwrites every\n`idp_*` field from the document.\n"
          },
          "idp_entity_id": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1024
          },
          "idp_sso_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "maxLength": 2048
          },
          "idp_slo_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "maxLength": 2048
          },
          "idp_x509_cert": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 16384,
            "description": "PEM. Rejected under `errors.sso` when it is not a readable X.509 certificate."
          },
          "oidc_issuer": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "maxLength": 2048
          },
          "oidc_client_id": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "oidc_client_secret": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1024,
            "description": "Write-only. The response only reports whether one is set.",
            "writeOnly": true
          },
          "attribute_map": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "email": {
                "type": "string",
                "maxLength": 255
              },
              "name": {
                "type": "string",
                "maxLength": 255
              },
              "groups": {
                "type": "string",
                "maxLength": 255
              }
            }
          },
          "jit_enabled": {
            "type": "boolean"
          },
          "default_role": {
            "type": "string",
            "enum": [
              "admin",
              "member",
              "viewer"
            ],
            "description": "`owner` is refused: ownership is not something an IdP provisions."
          },
          "group_role_map": {
            "type": "object",
            "maxProperties": 200,
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "ComplianceSettings": {
        "type": "object",
        "title": "Compliance settings",
        "description": "The workspace's compliance switches, what the plan makes available, and the values each\nsetting accepts — so a settings page needs one call, not four.\n",
        "additionalProperties": true,
        "properties": {
          "cfr_part11": {
            "type": "boolean"
          },
          "cfr_part11_signing_mode": {
            "type": [
              "string",
              "null"
            ],
            "description": "`null` means no workspace override; `cfr_part11_signing_mode_resolved` says what applies.",
            "enum": [
              "streamlined",
              "per_signature",
              null
            ]
          },
          "cfr_part11_signing_mode_resolved": {
            "type": "string",
            "enum": [
              "streamlined",
              "per_signature"
            ]
          },
          "nom151_enabled": {
            "type": "boolean"
          },
          "id_verification_required": {
            "type": "boolean"
          },
          "id_verification_provider": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "stripe_identity",
              "persona",
              null
            ]
          },
          "available": {
            "type": "object",
            "description": "What the plan carries. Note the key here is `nom151`, not `nom151_enabled`.",
            "additionalProperties": true,
            "properties": {
              "cfr_part11": {
                "type": "boolean"
              },
              "nom151": {
                "type": "boolean"
              },
              "id_verification": {
                "type": "boolean"
              }
            }
          },
          "signing_modes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "id_verification_providers": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ComplianceSettingsWrite": {
        "type": "object",
        "title": "Compliance settings",
        "description": "Only the keys present change. Turning something off is always allowed.",
        "additionalProperties": true,
        "properties": {
          "cfr_part11": {
            "type": "boolean"
          },
          "cfr_part11_signing_mode": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "streamlined",
              "per_signature",
              null
            ]
          },
          "nom151_enabled": {
            "type": "boolean"
          },
          "id_verification_required": {
            "type": "boolean"
          },
          "id_verification_provider": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "stripe_identity",
              "persona",
              null
            ]
          }
        }
      },
      "RegionChange": {
        "type": "object",
        "title": "Storage region change",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "storage_region": {
            "type": "string",
            "description": "Where the files are now. It keeps reporting the old region until the move finishes."
          },
          "requested_region": {
            "type": "string"
          },
          "effective_region": {
            "type": "string",
            "description": "Where a write would land right now. It can differ from both of the above when the plan\nno longer carries the region the workspace asked for.\n"
          },
          "changed": {
            "type": "boolean",
            "description": "False when the workspace was already there and nothing was queued."
          },
          "migration": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "status": {
                "type": "string",
                "enum": [
                  "pending",
                  "copying",
                  "verified",
                  "completed",
                  "failed",
                  "skipped"
                ]
              },
              "from_region": {
                "type": "string"
              },
              "to_region": {
                "type": "string"
              },
              "objects_total": {
                "type": "integer"
              },
              "objects_copied": {
                "type": "integer"
              },
              "bytes_copied": {
                "type": "integer"
              },
              "verified_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "originals_deleted_after": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "originals_deleted_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        },
        "required": [
          "id",
          "storage_region",
          "changed"
        ]
      },
      "ApiRequestLogEntry": {
        "type": "object",
        "title": "API request",
        "description": "One call made with this key. Bodies are never stored, and query strings are redacted as they\nare written — a secret that arrived in a URL does not become a second copy of the secret.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "request_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "method": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "query": {
            "type": [
              "string",
              "null"
            ],
            "description": "Redacted at write time; sensitive values read `[redacted]`."
          },
          "status": {
            "type": "integer"
          },
          "latency_ms": {
            "type": "integer"
          },
          "error_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "ip": {
            "type": [
              "string",
              "null"
            ]
          },
          "user_agent": {
            "type": [
              "string",
              "null"
            ]
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "copy_as_curl": {
            "type": "string",
            "description": "The request as a `curl` command, ready to paste."
          }
        },
        "required": [
          "id",
          "method",
          "path",
          "status",
          "occurred_at"
        ]
      },
      "ScimUser": {
        "type": "object",
        "title": "SCIM user",
        "description": "RFC 7643 `User`. `id` is the **membership** id — Signclad's own identifier, and the one\nevery other SCIM route takes in its path. `externalId` is the **directory's** identifier,\nstored from the create request and echoed back unchanged; it is **absent** — not null, not\na substitute — when the directory never sent one.\n\nOnly `active` is patchable. `roles` carries exactly one entry.\n",
        "additionalProperties": true,
        "properties": {
          "schemas": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "id": {
            "type": "string"
          },
          "externalId": {
            "type": "string",
            "description": "The IdP's own handle, as it was supplied on create. Omitted entirely when there is\nnone.\n"
          },
          "userName": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "formatted": {
                "type": "string"
              },
              "givenName": {
                "type": "string"
              },
              "familyName": {
                "type": "string"
              }
            }
          },
          "displayName": {
            "type": "string"
          },
          "emails": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "value": {
                  "type": "string",
                  "format": "email"
                },
                "type": {
                  "type": "string"
                },
                "primary": {
                  "type": "boolean"
                }
              }
            }
          },
          "active": {
            "type": "boolean"
          },
          "roles": {
            "type": "array",
            "description": "One entry. `owner` is downgraded to `admin` on the way in.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "value": {
                  "type": "string",
                  "enum": [
                    "owner",
                    "admin",
                    "member",
                    "viewer"
                  ]
                },
                "primary": {
                  "type": "boolean"
                }
              }
            }
          },
          "meta": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "resourceType": {
                "type": "string"
              },
              "created": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "lastModified": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "location": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "ScimListResponse": {
        "type": "object",
        "title": "SCIM list response",
        "description": "`itemsPerPage` is the **page size** — what to add to `startIndex` to ask for the next page —\nand not the number of rows on this page, so it does not shrink on a short last page. It did\nonce, and a provider doing that arithmetic read the short page as the end of the\ncollection.\n\n`totalResults` counts only rows that can actually be returned. A membership whose user\nrecord has gone is excluded from both the count and `Resources`, so the two never promise a\npage that does not arrive.\n",
        "additionalProperties": true,
        "properties": {
          "schemas": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "totalResults": {
            "type": "integer"
          },
          "startIndex": {
            "type": "integer"
          },
          "itemsPerPage": {
            "type": "integer"
          },
          "Resources": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScimUser"
            }
          }
        },
        "required": [
          "schemas",
          "totalResults",
          "Resources"
        ]
      },
      "ScimPatchRequest": {
        "type": "object",
        "title": "SCIM patch",
        "description": "`Operations` and `operations` are both read; `replace` and `add` both work. The only path\nthat can change is `active`, either as `path: \"active\"` or as a value object carrying it.\n",
        "additionalProperties": true,
        "properties": {
          "schemas": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "Operations": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "op": {
                  "type": "string",
                  "enum": [
                    "replace",
                    "add"
                  ]
                },
                "path": {
                  "type": "string"
                },
                "value": {}
              }
            }
          }
        }
      },
      "ScimErrorBody": {
        "type": "object",
        "title": "SCIM error",
        "description": "RFC 7644's error envelope, not Signclad's. Note `status` is a **string**, and `scimType` is\nabsent rather than null when there is none.\n",
        "additionalProperties": true,
        "properties": {
          "schemas": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "status": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          },
          "scimType": {
            "type": "string",
            "enum": [
              "uniqueness",
              "invalidFilter",
              "invalidValue"
            ]
          }
        },
        "required": [
          "schemas",
          "status"
        ]
      },
      "PendingEmailChange": {
        "type": "object",
        "title": "Pending email change",
        "description": "All three are `null` when nothing is pending.",
        "additionalProperties": true,
        "properties": {
          "pending_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "verification_sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ConfirmedEmailChange": {
        "title": "Confirmed email change",
        "description": "The pending-change shape plus the two fields only the confirmation carries.",
        "allOf": [
          {
            "$ref": "#/components/schemas/PendingEmailChange"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "confirmed": {
                "type": "boolean",
                "const": true
              },
              "email": {
                "type": "string",
                "format": "email",
                "description": "The address the account now signs in with."
              }
            },
            "required": [
              "confirmed",
              "email"
            ]
          }
        ]
      },
      "NotificationPreferences": {
        "type": "object",
        "title": "Notification preferences",
        "description": "One person's mail settings **in one workspace**. Every key is always present on the way out;\non the way in, anything omitted keeps its value.\n",
        "additionalProperties": true,
        "properties": {
          "document_sent": {
            "type": "boolean",
            "default": true
          },
          "document_viewed": {
            "type": "boolean",
            "default": true
          },
          "document_signed": {
            "type": "boolean",
            "default": true
          },
          "document_completed": {
            "type": "boolean",
            "default": true
          },
          "document_declined": {
            "type": "boolean",
            "default": true
          },
          "document_bounced": {
            "type": "boolean",
            "default": true
          },
          "document_expiring": {
            "type": "boolean",
            "default": true
          },
          "reminders_digest": {
            "type": "boolean",
            "default": true
          },
          "team_activity": {
            "type": "boolean",
            "default": true
          },
          "webhook_health": {
            "type": "boolean",
            "default": true
          },
          "product_updates": {
            "type": "boolean",
            "default": false,
            "description": "The only switch that is off to begin with."
          },
          "monthly_activity_digest": {
            "type": "boolean",
            "default": true,
            "description": "A monthly summary of what the workspace sent."
          },
          "digest": {
            "type": "string",
            "enum": [
              "off",
              "daily",
              "weekly"
            ],
            "default": "off"
          }
        }
      },
      "SavedSignature": {
        "type": "object",
        "title": "Saved signature",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "signature",
              "initials"
            ]
          },
          "source": {
            "type": "string",
            "description": "How it was made, in the past tense.",
            "enum": [
              "drawn",
              "typed",
              "uploaded"
            ]
          },
          "method": {
            "type": "string",
            "description": "The same fact in the imperative, for clients that send it that way.",
            "enum": [
              "draw",
              "type",
              "upload"
            ]
          },
          "preview_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Short-lived signed URL, about ten minutes. `null` on a driver that cannot sign one."
          },
          "font": {
            "type": [
              "string",
              "null"
            ],
            "description": "Only for a typed signature."
          },
          "is_default": {
            "type": "boolean"
          },
          "default": {
            "type": "boolean",
            "description": "The same value as `is_default`."
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "kind"
        ]
      },
      "SaveSignatureRequest": {
        "type": "object",
        "title": "Save a signature",
        "description": "Send exactly one of `file` (a PNG upload), `data` (a PNG data URL), or `text` — the last\nwith an optional `font`. `method` and `source` are interchangeable and each takes either\ntense; at least one must be present.\n",
        "additionalProperties": true,
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "signature",
              "initials"
            ]
          },
          "method": {
            "type": "string",
            "enum": [
              "draw",
              "drawn",
              "type",
              "typed",
              "upload",
              "uploaded"
            ]
          },
          "source": {
            "type": "string",
            "description": "Alias of `method`.",
            "enum": [
              "draw",
              "drawn",
              "type",
              "typed",
              "upload",
              "uploaded"
            ]
          },
          "data": {
            "type": [
              "string",
              "null"
            ],
            "description": "A `data:image/png;base64,…` URL."
          },
          "file": {
            "type": "string",
            "format": "binary",
            "description": "Multipart only. PNG, at most 5 MB."
          },
          "text": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "The name to render, for a typed signature."
          },
          "font": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 60
          },
          "default": {
            "type": "boolean"
          },
          "is_default": {
            "type": "boolean",
            "description": "Alias of `default`; either sets it."
          }
        },
        "required": [
          "kind"
        ]
      },
      "Contact": {
        "type": "object",
        "title": "Contact",
        "description": "An entry in the workspace's address book. `name` and `email` are always strings — a row with\nno name reads `\"\"` here, though the same person reads `null` under `/me`'s `contact`.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "company": {
            "type": [
              "string",
              "null"
            ]
          },
          "job_title": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "documents_count": {
            "type": "integer",
            "description": "How many documents this address appears on."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "name",
          "email"
        ]
      },
      "ContactSuggestion": {
        "type": "object",
        "title": "Contact suggestion",
        "description": "The slimmer object type-ahead returns — no counts, no timestamps.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "company": {
            "type": [
              "string",
              "null"
            ]
          },
          "job_title": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone_number": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "email"
        ]
      },
      "ContactWrite": {
        "type": "object",
        "title": "Contact details",
        "description": "On a create, `name` and `email` must both be present — though `name` may be `null`, for the\naddress you have without the person's name yet. On an update, only what is present changes.\n",
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 255
          },
          "company": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "job_title": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255
          },
          "phone_number": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 32
          }
        }
      },
      "ContactImportResult": {
        "type": "object",
        "title": "Contact import result",
        "description": "A partial import is the normal outcome, so this is a `200` even with rows in `errors`.\n\nEach row error carries the same fact twice — flat as `column` and `error`, keyed by column\nas `errors` — plus a `message` that reads as one sentence. The leaf under `errors` is a\n**string**, like every other `errors` leaf in this API; it held a single-element array\nuntil the error-envelope pass, which was the one place that broke the no-arrays rule.\n",
        "additionalProperties": true,
        "properties": {
          "imported": {
            "type": "integer"
          },
          "updated": {
            "type": "integer"
          },
          "skipped": {
            "type": "integer"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "row": {
                  "type": "integer",
                  "description": "1-based, header excluded."
                },
                "column": {
                  "type": "string",
                  "description": "The header exactly as the file spells it."
                },
                "error": {
                  "type": "string"
                },
                "errors": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                },
                "message": {
                  "type": "string"
                }
              }
            }
          },
          "unknown_columns": {
            "type": "array",
            "description": "Headers nothing claimed. Ignored, not fatal.",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "imported",
          "updated",
          "skipped"
        ]
      },
      "DataExport": {
        "type": "object",
        "title": "Data export",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "`expired` is derived: a `ready` export past `expires_at` reports as expired before\nanything sweeps it up.\n",
            "enum": [
              "queued",
              "building",
              "ready",
              "expired",
              "failed"
            ]
          },
          "scope": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "documents",
                "templates",
                "contacts",
                "audit"
              ]
            }
          },
          "requested_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ready_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "download_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Short-lived and signed, so it differs between reads. Present only while the export is\ndownloadable, and `null` on a storage driver that cannot sign a URL.\n"
          },
          "size_bytes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "document_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error_code": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "build_failed",
              "workspace_missing",
              "no_temporary_storage"
            ]
          }
        },
        "required": [
          "id",
          "status",
          "scope"
        ]
      },
      "EventCatalog": {
        "type": "object",
        "title": "Event catalog",
        "description": "Every event Signclad emits, plus the two things a receiver has to implement: how to\nverify a delivery, and what to expect from the retry schedule. Generated from the same\nsource as the delivery pipeline, so it can be trusted to be complete.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EventCatalogEntry"
            }
          },
          "signature": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "header": {
                "type": "string"
              },
              "scheme": {
                "type": "string"
              },
              "tolerance_seconds": {
                "type": "integer"
              },
              "compatibility_hash": {
                "type": "string",
                "description": "The compatibility `event.hash` scheme, keyed by the endpoint id."
              }
            }
          },
          "delivery": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "timeout_seconds": {
                "type": "integer"
              },
              "success": {
                "type": "string"
              },
              "retries": {
                "type": "array",
                "description": "Delay in seconds before each attempt after the first.",
                "items": {
                  "type": "integer"
                }
              },
              "dedupe_on": {
                "type": "string"
              },
              "order_by": {
                "type": "string"
              }
            }
          }
        },
        "required": [
          "data"
        ]
      },
      "WebhookReplaySummary": {
        "type": "object",
        "title": "Replay summary",
        "description": "What a range replay queued. `POST /webhook_endpoints/{id}/enable` reports a **different**\nshape — see `WebhookEnableReplay` — because it is answering a different question.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "replayed": {
            "type": "integer",
            "description": "Deliveries re-queued."
          },
          "delivery_ids": {
            "type": "array",
            "description": "The id of each new delivery attempt, in the order they were queued. A range can cover\nthousands, so expect a long array.\n",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "replayed"
        ]
      },
      "WebhookEnableReplay": {
        "type": "object",
        "title": "Enable replay offer",
        "description": "What `POST /webhook_endpoints/{id}/enable` reports: how much the endpoint missed while it\nwas off, and how much of it was just re-queued. `replayed_count` is `0` when the request\ndid not ask for a replay, which is how a caller shows \"312 events are waiting\" before\ndeciding.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "available_since": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the endpoint stopped receiving. `null` when it missed nothing."
          },
          "available_count": {
            "type": "integer",
            "description": "How many deliveries it missed and could still be replayed."
          },
          "replayed_count": {
            "type": "integer",
            "description": "How many this call actually re-queued."
          }
        },
        "required": [
          "available_count",
          "replayed_count"
        ]
      },
      "WebhookInbox": {
        "type": "object",
        "title": "Webhook inbox",
        "description": "A hosted URL that records whatever is posted to it, so a developer can see real payloads\nbefore writing a receiver.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The public ingest URL. Its token is the whole credential."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "url"
        ]
      },
      "WebhookInboxRequest": {
        "type": "object",
        "title": "Recorded request",
        "description": "One request an inbox recorded, exactly as it arrived.",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "method": {
            "type": "string"
          },
          "headers": {
            "type": "object",
            "additionalProperties": true
          },
          "query": {
            "type": "object",
            "additionalProperties": true
          },
          "content_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "body": {
            "type": [
              "string",
              "null"
            ]
          },
          "ip": {
            "type": [
              "string",
              "null"
            ]
          },
          "received_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "method"
        ]
      },
      "InboundResult": {
        "type": "object",
        "title": "Inbound result",
        "description": "What the provider report did. `unmatched` is a success: it means this environment never\nsent the message the report is about.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "status": {
            "type": "string",
            "examples": [
              "processed",
              "unmatched",
              "ignored",
              "confirmed"
            ]
          }
        },
        "required": [
          "status"
        ]
      },
      "TemplateRevision": {
        "type": "object",
        "title": "Template revision",
        "description": "One saved snapshot of a template: files, placeholders, fields, groups, rules, settings.\nDocuments pin the revision ids they were built from, so publishing a new one never moves\na document that already exists.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "number": {
            "type": "integer"
          },
          "reason": {
            "type": "string",
            "enum": [
              "upload",
              "edit",
              "replace_file",
              "restore"
            ]
          },
          "current": {
            "type": "boolean"
          },
          "snapshot_sha256": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "number"
        ]
      },
      "SignerError": {
        "type": "object",
        "title": "Signer error",
        "description": "Every refusal the signer API can hand back, in one envelope. The signer app switches on\n`meta.error` to choose which page to show, so the code is part of the contract, not a log\nstring. `meta.message` and `meta.messages[]` repeat the message, matching the\ncompatibility error shape.\n",
        "additionalProperties": true,
        "properties": {
          "message": {
            "type": "string"
          },
          "meta": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "error": {
                "type": "string",
                "description": "Gate: `passcode_required`, `passcode_invalid`, `passcode_locked`,\n`not_your_turn`. Link: `link_not_found`, `link_revoked`, `link_expired`,\n`link_already_used`, `link_superseded`. Document: `document_expired`,\n`document_canceled`, `document_declined`, `document_not_sent`,\n`document_finalizing`, `document_not_active`, `revision_mismatch`. Recipient —\nthis session's own recipient is finished, while the document may still be open\nfor others: `recipient_declined`, `recipient_reassigned`,\n`recipient_already_signed`. Action: `consent_required`, `decline_not_allowed`,\n`reassign_not_allowed`, `download_not_available`, `scope_not_allowed`.\nCompliance gates: `reauthentication_required`, `reauthentication_invalid`,\n`reauthentication_unavailable`, `reauthentication_expired`,\n`id_verification_required`, `id_verification_incomplete`,\n`id_verification_not_requested`.\n"
              },
              "message": {
                "type": "string"
              },
              "messages": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "state": {
                "type": "string",
                "description": "The document's internal state, on the document-level codes."
              },
              "scope": {
                "type": "string",
                "description": "The session's scope, on `scope_not_allowed`."
              },
              "waiting_on_signing_order": {
                "type": "integer",
                "description": "The signing-order position still to sign, on `not_your_turn`."
              },
              "retry_after_seconds": {
                "type": "integer",
                "description": "Seconds until the passcode gate accepts another attempt, on `passcode_locked`."
              },
              "sent_revision_hash": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The revision the session was opened against, on `revision_mismatch`. Compare it\nwith the document's current `sent_revision_hash` to see what moved.\n"
              },
              "cfr_part11_signing_mode": {
                "type": "string",
                "description": "On the re-authentication codes, which Part 11 mode is in force."
              },
              "fields": {
                "type": "array",
                "description": "The `api_id` of each field still needing re-authentication, on a `per_signature`\nPart 11 document.\n",
                "items": {
                  "type": "string"
                }
              },
              "status": {
                "type": "string",
                "description": "How far the identity check got, on `id_verification_incomplete`."
              }
            }
          },
          "request_id": {
            "type": "string",
            "description": "Always present on this envelope."
          }
        },
        "required": [
          "message",
          "meta",
          "request_id"
        ]
      },
      "SignerAttachment": {
        "type": "object",
        "title": "Signer attachment",
        "description": "An attachment request as the signer app sees it, and what `POST\n/signer/attachments/{attachment_request_id}` answers.\n\n`id` and `attachment_request_id` are the **same** value — the attachment request's id —\nwhich is not what `id` means on the sender-side `Attachment`, where it identifies the\nuploaded file. Address this surface by `attachment_request_id` and the ambiguity goes\naway.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "attachment_request_id": {
            "type": "string"
          },
          "recipient_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "required": {
            "type": "boolean"
          },
          "mime": {
            "type": [
              "string",
              "null"
            ],
            "description": "`null` until something has been uploaded."
          },
          "size": {
            "type": [
              "integer",
              "null"
            ],
            "description": "`null` until something has been uploaded."
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Short-lived link to the uploaded file, or `null` when there is none yet."
          },
          "uploaded_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "attachment_request_id",
          "name",
          "required"
        ]
      },
      "SignerExchangeRequest": {
        "type": "object",
        "title": "Exchange a link",
        "description": "The opaque token from a signing or embedded link, plus whatever the gate asked for on a\nprevious attempt.\n",
        "additionalProperties": true,
        "properties": {
          "token": {
            "type": "string",
            "maxLength": 512,
            "description": "The token out of the link. It is the credential."
          },
          "passcode": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 128
          }
        },
        "required": [
          "token"
        ]
      },
      "SignerSessionToken": {
        "type": "object",
        "title": "Signer session",
        "description": "The bearer token for every other signer call. The plaintext exists in this response and\nnowhere else; the signer app holds it in memory and `sessionStorage`. No cookie is set,\nso it survives third-party iframe cookie partitioning.\n",
        "additionalProperties": true,
        "properties": {
          "token": {
            "type": "string"
          },
          "token_type": {
            "type": "string",
            "const": "Bearer"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "30 minutes out, and slid forward by every authenticated signer call."
          },
          "scope": {
            "type": "string",
            "enum": [
              "sign",
              "link",
              "in_person",
              "preview",
              "document:edit",
              "template:edit"
            ]
          },
          "document_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "template_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "recipient_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The internal recipient id this session is bound to."
          },
          "session_id": {
            "type": "string"
          },
          "id_verification_required": {
            "type": "boolean",
            "description": "The signer must complete `POST /signer/id_verification/start` before signing. Present\non `POST /signer/exchange` only — `GET /signer/session` reports the same facts under\n`gate`.\n"
          },
          "reauthentication_required": {
            "type": "boolean",
            "description": "The signer must re-authenticate before each signature. Exchange response only."
          },
          "cfr_part11": {
            "type": "boolean",
            "description": "The document is under 21 CFR Part 11 rules. Exchange response only."
          },
          "cfr_part11_signing_mode": {
            "type": [
              "string",
              "null"
            ],
            "description": "How Part 11 signing is performed on this document. Exchange response only."
          }
        },
        "required": [
          "token",
          "token_type",
          "scope"
        ]
      },
      "SignerDownload": {
        "type": "object",
        "title": "Signed copy",
        "description": "A short-lived link to the sealed `completed` artifact, with its hash.",
        "additionalProperties": true,
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "kind": {
            "type": "string",
            "const": "completed"
          },
          "sha256": {
            "type": [
              "string",
              "null"
            ],
            "description": "SHA-256 of the artifact, the same value the verification page matches on."
          },
          "size": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Bytes."
          },
          "expires_in": {
            "type": "integer",
            "description": "Seconds the link stays valid."
          }
        },
        "required": [
          "url",
          "kind"
        ]
      },
      "SignerField": {
        "type": "object",
        "title": "Signer field",
        "description": "A field as the signer app sees it. Same values as the sender API — a checked checkbox\nreads `\"t\"`, a date reads through its `date_format` — but `width` and `height` are numbers\nhere rather than the sender API's decimal strings, because this surface is ours and\nnothing on it has to stay bug-compatible. `x` and `y` are numbers on both surfaces.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "api_id": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/FieldType"
          },
          "recipient_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "template_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "file_position": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "x": {
            "type": "number"
          },
          "y": {
            "type": "number"
          },
          "width": {
            "type": "number"
          },
          "height": {
            "type": "number"
          },
          "required": {
            "type": "boolean"
          },
          "label": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "value": {
            "type": [
              "string",
              "number",
              "boolean",
              "null"
            ]
          },
          "signature_asset_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "validation": {
            "type": [
              "string",
              "null"
            ]
          },
          "validation_regex": {
            "type": [
              "string",
              "null"
            ]
          },
          "validation_regex_error_message": {
            "type": [
              "string",
              "null"
            ]
          },
          "fixed_width": {
            "type": "boolean"
          },
          "lock_sign_date": {
            "type": "boolean"
          },
          "date_format": {
            "type": [
              "string",
              "null"
            ]
          },
          "formula": {
            "type": [
              "string",
              "null"
            ]
          },
          "options": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FieldOption"
            }
          },
          "default_option": {
            "type": [
              "string",
              "null"
            ]
          },
          "allow_other": {
            "type": "boolean"
          },
          "group_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The checkbox group this field belongs to."
          },
          "pre_signed": {
            "type": "boolean",
            "description": "Signature or initials content copied in from the template. Visible, produced no\nsigning event, and not this recipient's to change — which is why `read_only` carries\nthe same value.\n"
          },
          "read_only": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "api_id",
          "type"
        ]
      },
      "SignatureAsset": {
        "type": "object",
        "title": "Signature asset",
        "description": "A stored signature or initials image, reusable for the rest of the session.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "signature",
              "initials"
            ]
          },
          "method": {
            "type": "string",
            "enum": [
              "drawn",
              "typed",
              "uploaded"
            ],
            "description": "How the signer made it. The **request** accepts either spelling — `draw`/`drawn`,\n`type`/`typed`, `upload`/`uploaded` — and stores and returns the past participle.\n"
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Short-lived link to the PNG. `null` if the artifact could not be signed."
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "kind"
        ]
      },
      "SignerPage": {
        "type": "object",
        "title": "Page geometry",
        "description": "One page, sized in field units (1 unit = 0.75 pt) so the overlay lines up with the render\nwithout the signer app converting anything.\n",
        "additionalProperties": true,
        "properties": {
          "file_position": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "width": {
            "type": "number"
          },
          "height": {
            "type": "number"
          },
          "rotation": {
            "type": "integer"
          },
          "render_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Short-lived signed URL for the server-side render of this page."
          }
        }
      },
      "SignerSessionView": {
        "type": "object",
        "title": "Signer view",
        "description": "What this session may see: the document's identity and settings, its files with page\ngeometry, this recipient only, this recipient's fields, the gate state, and which actions\nare allowed. Other recipients' values appear only after completion, in\n`completed_fields`, as display values with no `api_id` to write back to.\n",
        "additionalProperties": true,
        "properties": {
          "document": {
            "type": "object",
            "additionalProperties": true,
            "description": "The subset of the document a signer is allowed to see.",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "status": {
                "$ref": "#/components/schemas/DocumentStatus"
              },
              "language": {
                "$ref": "#/components/schemas/Language"
              },
              "allow_decline": {
                "type": "boolean"
              },
              "allow_reassign": {
                "type": "boolean"
              },
              "with_signature_page": {
                "type": "boolean"
              },
              "redirect_url": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "decline_redirect_url": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "sent_revision_hash": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "expires_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "test_mode": {
                "type": "boolean"
              },
              "cfr_part11": {
                "type": "boolean"
              },
              "cfr_part11_signing_mode": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "recipient": {
            "type": "object",
            "additionalProperties": true,
            "description": "This session's recipient. `id` is the internal id here and `external_id` is the\ncaller-supplied one the sender API calls `id`.\n",
            "properties": {
              "id": {
                "type": "string"
              },
              "external_id": {
                "type": "string"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "role": {
                "type": "string",
                "enum": [
                  "signer",
                  "cc"
                ]
              },
              "status": {
                "$ref": "#/components/schemas/RecipientStatus"
              },
              "signing_order": {
                "type": "integer"
              }
            }
          },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string"
                },
                "position": {
                  "type": "integer"
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "pages_number": {
                  "type": "integer"
                },
                "status": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "pages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SignerPage"
            }
          },
          "fields": {
            "type": "array",
            "description": "This recipient's fields only, with their saved values.",
            "items": {
              "$ref": "#/components/schemas/SignerField"
            }
          },
          "completed_fields": {
            "type": "array",
            "description": "Other recipients' work, after completion only, as display values — enough to draw the\nfinished page and nothing more. Empty until the document is complete.\n",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "checkbox_groups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CheckboxGroup"
            }
          },
          "conditional_rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConditionalRule"
            }
          },
          "attachment_requests": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SignerAttachment"
            }
          },
          "signing_order": {
            "type": "object",
            "additionalProperties": true,
            "description": "Where this recipient sits in the order, and whether anybody is still ahead.",
            "properties": {
              "applies": {
                "type": "boolean",
                "description": "False when the document has no enforced order."
              },
              "position": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "total": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "waiting_on_others": {
                "type": "boolean",
                "description": "True while somebody earlier in the order has not finished."
              }
            }
          },
          "gate": {
            "type": "object",
            "additionalProperties": true,
            "description": "What stood between the signer and the document. `passcode_required` is `false` by\nthe time a session exists — the exchange enforced it — and is reported so the app\ncan explain how the document was opened.\n",
            "properties": {
              "consent_required": {
                "type": "boolean"
              },
              "consent_recorded_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "passcode_required": {
                "type": "boolean"
              },
              "id_verification_required": {
                "type": "boolean"
              },
              "id_verification_status": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Where the identity check has got to, or `null` when none is required."
              },
              "reauthentication_required": {
                "type": "boolean",
                "description": "Whether the signer must re-authenticate before each signature."
              },
              "reauthentication_method": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Which method the workspace requires, or `null` when none is required."
              }
            }
          },
          "allowed_actions": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "decline": {
                "type": "boolean"
              },
              "reassign": {
                "type": "boolean"
              },
              "download": {
                "type": "boolean"
              }
            }
          },
          "session": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "expires_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "scope": {
                "type": "string"
              },
              "can_download": {
                "type": "boolean"
              }
            }
          }
        },
        "required": [
          "document",
          "recipient"
        ]
      },
      "SignerFieldUpdateRequest": {
        "type": "object",
        "title": "Save field",
        "description": "The value to store. `signature_asset_id` applies a stored signature or initials image to\na signature or initials field. `value` must be present, and may be `null` to clear.\n",
        "additionalProperties": true,
        "properties": {
          "value": {
            "type": [
              "string",
              "number",
              "boolean",
              "null"
            ]
          },
          "signature_asset_id": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "value"
        ]
      },
      "SignerConsentRequest": {
        "type": "object",
        "title": "Record consent",
        "description": "The signer's agreement to sign electronically, bound to the revision they were shown. A\n`sent_revision_hash` that does not match the document's is `409 revision_mismatch`.\n",
        "additionalProperties": true,
        "properties": {
          "agreed": {
            "type": "boolean",
            "const": true
          },
          "sent_revision_hash": {
            "type": "string",
            "minLength": 64,
            "maxLength": 64
          },
          "signature_default_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "The name the signer confirmed for a typed signature."
          }
        },
        "required": [
          "agreed",
          "sent_revision_hash"
        ]
      },
      "SignerSignatureRequest": {
        "type": "object",
        "title": "Store signature",
        "description": "A drawn, typed, or uploaded signature image. `method` accepts both the short spelling\n(`draw`, `type`, `upload`) and the past-tense one (`drawn`, `typed`, `uploaded`).\n",
        "additionalProperties": true,
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "signature",
              "initials"
            ]
          },
          "method": {
            "type": "string",
            "enum": [
              "draw",
              "type",
              "upload",
              "drawn",
              "typed",
              "uploaded"
            ]
          },
          "data": {
            "type": [
              "string",
              "null"
            ],
            "contentEncoding": "base64",
            "description": "PNG bytes for `draw` and `upload`. Alias of `image_base64`."
          },
          "image_base64": {
            "type": [
              "string",
              "null"
            ],
            "contentEncoding": "base64",
            "description": "PNG bytes for `draw` and `upload`."
          },
          "typed_text": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "The text to render, for `type`."
          },
          "typed_font": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 60,
            "description": "Which licensed script face to render it in."
          }
        },
        "required": [
          "kind",
          "method"
        ]
      },
      "SignerSubmitRequest": {
        "type": "object",
        "title": "Submit",
        "description": "Optional final values, applied before validation. Sending them here is equivalent to\nsaving each field first.\n",
        "additionalProperties": true,
        "properties": {
          "fields": {
            "type": "array",
            "maxItems": 500,
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "api_id": {
                  "type": "string"
                },
                "value": {
                  "type": [
                    "string",
                    "number",
                    "boolean",
                    "null"
                  ]
                },
                "signature_asset_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "api_id",
                "value"
              ]
            }
          }
        }
      },
      "SignerSubmitResult": {
        "type": "object",
        "title": "Submit result",
        "description": "Where the signer goes next, and what happened to the document. `document_status` reads\n`Pending` while the seal job is running.\n",
        "additionalProperties": true,
        "properties": {
          "recipient_status": {
            "$ref": "#/components/schemas/RecipientStatus"
          },
          "document_status": {
            "$ref": "#/components/schemas/DocumentStatus"
          },
          "redirect_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "can_download": {
            "type": "boolean"
          },
          "conditional_logic_decisions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConditionalLogicDecision"
            }
          },
          "submission_hash": {
            "type": "string",
            "description": "Hash of the values this recipient submitted, bound to `sent_revision_hash` and\nrecorded in the audit chain.\n"
          }
        },
        "required": [
          "recipient_status",
          "document_status"
        ]
      },
      "SignerDeclineRequest": {
        "type": "object",
        "title": "Decline",
        "description": "The reason, stored on the document as `decline_message`.",
        "additionalProperties": true,
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          }
        },
        "required": [
          "reason"
        ]
      },
      "SignerReassignRequest": {
        "type": "object",
        "title": "Reassign",
        "description": "Who should sign instead.",
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          }
        },
        "required": [
          "email"
        ]
      },
      "SignerActionResult": {
        "type": "object",
        "title": "Signer action result",
        "description": "What a decline or a reassignment left behind.",
        "additionalProperties": true,
        "properties": {
          "recipient_status": {
            "$ref": "#/components/schemas/RecipientStatus"
          },
          "document_status": {
            "$ref": "#/components/schemas/DocumentStatus"
          }
        },
        "required": [
          "recipient_status",
          "document_status"
        ]
      },
      "SignerDeclineResult": {
        "title": "Decline result",
        "description": "The decline redirect, falling back to the document's ordinary `redirect_url` when there\nis no decline-specific one.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/SignerActionResult"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "redirect_url": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "reason": {
                "type": "string"
              }
            }
          }
        ]
      },
      "SignerReassignResult": {
        "title": "Reassign result",
        "description": "The replacement recipient. This session is revoked by the time it is returned.",
        "allOf": [
          {
            "$ref": "#/components/schemas/SignerActionResult"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "reassigned_to": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        ]
      },
      "EmbedContext": {
        "type": "object",
        "title": "Embed context",
        "description": "What an embedded page needs before it renders. It drives the `SC-document.setId` message\nthe page sends its host.\n",
        "additionalProperties": true,
        "x-signclad-extension": true,
        "properties": {
          "editor_features": {
            "type": "object",
            "description": "Server-resolved permissions for paid editor controls. Basic checkbox groups are available on every plan.",
            "properties": {
              "conditional_rules": {
                "type": "boolean"
              },
              "data_validation": {
                "type": "boolean"
              }
            }
          },
          "view": {
            "type": "string",
            "description": "Which view to render. Taken from the session's `start` claim when it names one,\notherwise derived from the scope.\n",
            "enum": [
              "document_builder",
              "template_builder",
              "edit_files",
              "edit_recipients",
              "recipient_side"
            ]
          },
          "document_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "template_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "scope": {
            "type": "string"
          },
          "can_customize_branding": {
            "type": "boolean",
            "description": "Whether this workspace may apply custom branding, including a trusted host accent override."
          },
          "api_app_styles": {
            "type": "object",
            "additionalProperties": true,
            "description": "The API application's branding, every key `null` when it has none.",
            "properties": {
              "primary_color": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "button_text_color": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "link_text_color": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "buttons_border_radius": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "logo_url": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              }
            }
          },
          "allowed_parent_origins": {
            "type": "array",
            "description": "Origins allowed to frame this page. The same list became this response's\n`Content-Security-Policy: frame-ancestors`, which defaults to `'none'`.\n",
            "items": {
              "type": "string"
            }
          },
          "iframe_flags": {
            "type": "array",
            "description": "Query flags the page accepts to mean \"I am embedded\". The canonical one is always\npresent; the compatibility alias is added when the workspace has the shim enabled.\n",
            "items": {
              "type": "string"
            }
          },
          "embed_globals": {
            "type": "array",
            "description": "The globals the embed script installs on `window`. The alias exists so a host that\nvendored the third-party script keeps working after changing only the URL.\n",
            "items": {
              "type": "string"
            }
          },
          "compat": {
            "type": "object",
            "additionalProperties": true,
            "description": "The `postMessage` vocabulary. `messages` maps each canonical message name to its emitted names.",
            "properties": {
              "messages": {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "outbound": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "inbound": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "new_edit_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "The next single-use builder URL, for an edit session only. A signer session renews by\nrefreshing its bearer token instead, so this is `null` there.\n"
          },
          "session": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "expires_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          }
        },
        "required": [
          "view"
        ]
      },
      "LabelRecord": {
        "type": "object",
        "title": "Stored label",
        "description": "A label as the workspace stores it. `#/components/schemas/Label` is the looser shape a\ndocument or template body may carry — a bare name, or an object without an id — whereas\nthis is always the saved row.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "color": {
            "type": [
              "string",
              "null"
            ],
            "description": "A `#rrggbb` hex colour, or `null` for the default."
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "LabelWrite": {
        "type": "object",
        "title": "Label write",
        "description": "What `POST /labels` and `PATCH /labels/{id}` accept.",
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100,
            "description": "Trimmed before it is stored, and unique within the workspace."
          },
          "color": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^#[0-9a-fA-F]{6}$",
            "description": "Six hex digits with a leading `#`, or `null`."
          }
        },
        "required": [
          "name"
        ]
      },
      "EmbeddedEditorDocumentSave": {
        "type": "object",
        "title": "Embedded editor document save",
        "description": "The keys an embedded document builder may write. Any other key is `422` naming that key —\nthis is the one place on the surface where an unrecognised property is refused rather\nthan ignored, because the refusal is the delegation boundary.\n",
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": "string"
          },
          "subject": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "recipients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Recipient"
            }
          },
          "fields": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/Field"
              }
            }
          },
          "checkbox_groups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CheckboxGroup"
            }
          },
          "conditional_rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConditionalRule"
            }
          }
        }
      },
      "EmbeddedEditorSendRequest": {
        "type": "object",
        "title": "Embedded editor send",
        "description": "The keys an embedded builder may set while sending. Narrower than\n`#/components/schemas/DocumentSendRequest`: no `files`, no `metadata`, no ownership or\narchival settings. Any other key is `422` naming that key.\n",
        "additionalProperties": true,
        "properties": {
          "subject": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "reminders": {
            "type": "boolean"
          },
          "test_mode": {
            "type": "boolean",
            "description": "May repeat the document's existing value; a different one is refused."
          },
          "expires_in": {
            "type": "integer"
          },
          "redirect_url": {
            "type": "string",
            "format": "uri"
          },
          "decline_redirect_url": {
            "type": "string",
            "format": "uri"
          },
          "copied_contacts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CopiedContact"
            }
          },
          "attachment_requests": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AttachmentRequest"
            }
          }
        }
      },
      "EmbeddedEditorTemplateSave": {
        "type": "object",
        "title": "Embedded editor template save",
        "description": "The keys an embedded template builder may write. Any other key is `422` naming that key.\n",
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": "string"
          },
          "placeholders": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Placeholder"
            }
          },
          "fields": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/Field"
              }
            }
          },
          "checkbox_groups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CheckboxGroup"
            }
          },
          "conditional_rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConditionalRule"
            }
          },
          "draft": {
            "type": "boolean",
            "description": "`true` returns the template to Draft; `false` publishes it."
          }
        }
      },
      "ActivityBatch": {
        "type": "object",
        "title": "Activity batch",
        "description": "A batch of recorded web app interactions.",
        "additionalProperties": true,
        "properties": {
          "expected_user_id": {
            "type": "string",
            "maxLength": 64,
            "description": "Who the batch was raised as. A mismatch is `409` and nothing is written."
          },
          "expected_workspace_id": {
            "type": "string",
            "maxLength": 64,
            "description": "Which workspace the batch was raised in. A mismatch is `409`."
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/ActivityEvent"
            }
          }
        },
        "required": [
          "events"
        ]
      },
      "ActivityEvent": {
        "type": "object",
        "title": "Activity event",
        "description": "One recorded interaction.",
        "additionalProperties": true,
        "properties": {
          "event_id": {
            "type": "string",
            "format": "uuid",
            "description": "Client-generated. A repeat of one already stored is dropped, not duplicated."
          },
          "kind": {
            "type": "string",
            "enum": [
              "click",
              "change",
              "submit",
              "navigation"
            ]
          },
          "route": {
            "type": "string",
            "maxLength": 255,
            "description": "The web app route pattern, not a filled-in URL."
          },
          "target": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255,
            "description": "What was interacted with, as a restricted selector-like string."
          },
          "resources": {
            "type": "array",
            "maxItems": 10,
            "description": "Ids the interaction touched. UUIDs or ULIDs only.",
            "items": {
              "type": "string"
            }
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "description": "Must fall within the last day, and no more than a minute in the future."
          }
        },
        "required": [
          "event_id",
          "kind",
          "route",
          "occurred_at"
        ]
      },
      "AdminActivityRow": {
        "type": "object",
        "title": "Admin activity row",
        "description": "One row of the staff activity log: a recorded interaction or a request-log entry, joined\nto the person who raised it.\n",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "integer",
            "description": "Also the pagination cursor."
          },
          "user_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "user_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "user_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "workspace_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "request",
              "click",
              "change",
              "submit",
              "navigation",
              null
            ]
          },
          "action": {
            "type": [
              "string",
              "null"
            ]
          },
          "route": {
            "type": [
              "string",
              "null"
            ]
          },
          "target": {
            "type": [
              "string",
              "null"
            ]
          },
          "resources": {
            "type": [
              "string",
              "null"
            ],
            "description": "A JSON-encoded array of ids, as stored."
          },
          "status": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The response status, for request-log rows."
          },
          "request_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "occurred_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "AdminUserRow": {
        "type": "object",
        "title": "Admin user row",
        "description": "One person on the staff people screen, with the decisions the panel needs.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "email_verified": {
            "type": "boolean"
          },
          "can_delete": {
            "type": "boolean",
            "description": "`false` for staff and for the caller themselves."
          },
          "can_impersonate": {
            "type": "boolean",
            "description": "`false` for staff, for anybody awaiting deletion, and for anybody with no live\nmembership to impersonate into.\n"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "workspaces": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "role": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "archived": {
                  "type": "boolean"
                },
                "plan": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "status": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "interval": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          }
        },
        "required": [
          "id",
          "email"
        ]
      },
      "AdminSentEmailSummary": {
        "type": "object",
        "title": "Sent message summary",
        "description": "The headers of one sent message. The body is on the detail route.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "subject": {
            "type": [
              "string",
              "null"
            ]
          },
          "from_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "to_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "mailable": {
            "type": [
              "string",
              "null"
            ],
            "description": "The mailable class that produced it."
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id"
        ]
      },
      "AdminSentEmail": {
        "title": "Sent message",
        "description": "The whole stored message, plus a sanitised rendering. The raw `html` is present as\nstored; `html_preview` is what is safe to put in an iframe.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/AdminSentEmailSummary"
          },
          {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "recipients": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Every address the message went to, as stored."
              },
              "html": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "text": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "html_preview": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Sanitised for embedding."
              }
            }
          }
        ]
      },
      "AdminUsageHistory": {
        "type": "object",
        "title": "Admin usage history",
        "description": "Documents sent by month, for a workspace and for the account above it.",
        "additionalProperties": true,
        "properties": {
          "months": {
            "type": "integer"
          },
          "range": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "from": {
                "type": "string",
                "description": "`YYYY-MM`."
              },
              "to": {
                "type": "string",
                "description": "`YYYY-MM`."
              }
            }
          },
          "api_sources": {
            "type": "array",
            "description": "The `documents.source` values that make a send an API send — the same list the usage\nmeter bills from.\n",
            "items": {
              "type": "string"
            }
          },
          "workspace": {
            "$ref": "#/components/schemas/AdminUsageSubject"
          },
          "account": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/AdminUsageSubject"
              },
              {
                "type": "null"
              }
            ],
            "description": "`null` when the workspace has no billing account."
          }
        }
      },
      "AdminUsageSubject": {
        "type": "object",
        "title": "Admin usage subject",
        "description": "One workspace's or account's usage series and its totals.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "plan": {
            "type": "string",
            "description": "Present on the account, absent on the workspace."
          },
          "workspaces": {
            "type": "integer",
            "description": "Present on the account, absent on the workspace."
          },
          "series": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "month": {
                  "type": "string",
                  "description": "`YYYY-MM`."
                },
                "api_documents": {
                  "type": "integer"
                },
                "documents": {
                  "type": "integer"
                }
              }
            }
          },
          "totals": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "api_documents": {
                "type": "integer"
              },
              "documents": {
                "type": "integer"
              },
              "total_documents": {
                "type": "integer"
              },
              "api_documents_per_month": {
                "type": "number"
              },
              "peak_api_documents": {
                "type": "integer"
              }
            }
          }
        }
      },
      "AdminBillingHistory": {
        "type": "object",
        "title": "Admin billing history",
        "description": "Plan, seats, period and recent invoices for one account. A Stripe outage, an\nunconfigured environment, and an account that has never been billed all answer with an\nempty `invoices` and a populated `invoices_error` rather than failing the request.\n",
        "additionalProperties": true,
        "properties": {
          "account": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "owner_email": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "email"
              },
              "billing_email": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "email"
              },
              "plan": {
                "type": "string"
              },
              "plan_interval": {
                "type": "string"
              },
              "plan_status": {
                "type": "string"
              },
              "enterprise": {
                "type": "boolean"
              },
              "read_only": {
                "type": "boolean"
              },
              "billed_seats": {
                "type": "integer"
              },
              "included_seats": {
                "type": "integer"
              },
              "current_period_start": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "current_period_end": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "trial_ends_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "invoices": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "invoices_error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why `invoices` is empty, when it is."
          }
        }
      },
      "AdminApiPricingOverride": {
        "type": "object",
        "title": "Admin API pricing override",
        "description": "`mode` decides which other field is required. `rate` needs `rate_usd`, `stripe_price`\nneeds `stripe_price_id`, and `default` needs neither and clears the override.\n",
        "additionalProperties": true,
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "default",
              "rate",
              "stripe_price"
            ]
          },
          "rate_usd": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{1,6}(?:\\.\\d{1,6})?$",
            "description": "A per-document price in dollars, as a decimal string. Required when `mode` is `rate`."
          },
          "stripe_price_id": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^price_[a-zA-Z0-9_]+$",
            "maxLength": 255,
            "description": "Required when `mode` is `stripe_price`."
          },
          "reason": {
            "type": "string",
            "minLength": 8,
            "maxLength": 500,
            "description": "Recorded with the staff member who made the override. Not optional."
          }
        },
        "required": [
          "mode",
          "reason"
        ]
      }
    },
    "examples": {
      "DocumentCreateSimple": {
        "summary": "One PDF, one signer, one signature field",
        "value": {
          "name": "Loan agreement",
          "subject": "Please sign your loan agreement",
          "message": "<p>Two signatures, about a minute.</p>",
          "test_mode": false,
          "draft": false,
          "reminders": true,
          "expires_in": 30,
          "language": "en",
          "metadata": {
            "loan_id": "48213"
          },
          "files": [
            {
              "name": "loan-agreement.pdf",
              "file_url": "https://files.example.com/loan-agreement.pdf?X-Amz-Expires=7200&X-Amz-Signature=..."
            }
          ],
          "recipients": [
            {
              "id": "signer-1",
              "name": "Jane Doe",
              "email": "jane@example.com"
            }
          ],
          "fields": [
            [
              {
                "api_id": "borrower_signature",
                "type": "signature",
                "recipient_id": "signer-1",
                "page": 1,
                "x": 375,
                "y": 206,
                "width": 180,
                "height": 44,
                "required": true
              },
              {
                "api_id": "borrower_date",
                "type": "date",
                "recipient_id": "signer-1",
                "page": 1,
                "x": 375,
                "y": 262,
                "width": 120,
                "height": 24,
                "lock_sign_date": true,
                "date_format": "MM/DD/YYYY"
              }
            ]
          ]
        }
      },
      "DocumentCreateEmbeddedDraft": {
        "summary": "Embedded draft opened in the document builder",
        "description": "`draft: true` returns a document-level `embedded_edit_url`. `embedded_signing: true`\nmakes the recipients' URLs embeddable once it is sent.\n",
        "value": {
          "name": "Onboarding packet",
          "draft": true,
          "embedded_signing": true,
          "embedded_signing_notifications": false,
          "text_tags": true,
          "custom_requester_name": "Acme Lending",
          "custom_requester_email": "docs@acme.example",
          "api_application_id": "5c1d9e7f-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
          "files": [
            {
              "name": "packet.docx",
              "file_url": "https://files.example.com/packet.docx?X-Amz-Expires=7200&X-Amz-Signature=..."
            }
          ],
          "recipients": [
            {
              "id": "signer-1",
              "name": "Jane Doe",
              "email": "jane@example.com",
              "send_email": false
            }
          ]
        }
      },
      "DocumentCreated": {
        "summary": "The 201 from a create",
        "description": "The status is the transient `Created`, whatever `draft` was, and `pages_number` is `0`.\nA `GET` a moment later reads `Draft` or `Sent` with the real page count.\n",
        "value": {
          "id": "8f14e45f-ea8f-4b2c-9f0e-1b2c3d4e5f60",
          "status": "Created",
          "archived": false,
          "name": "Loan agreement",
          "subject": "Please sign your loan agreement",
          "message": "<p>Two signatures, about a minute.</p>",
          "test_mode": false,
          "draft": false,
          "embedded_signing": true,
          "apply_signing_order": false,
          "allow_decline": true,
          "allow_reassign": true,
          "language": "en",
          "expires_in": 30,
          "reminders": true,
          "created_at": "2026-09-09T17:44:10Z",
          "updated_at": "2026-09-09T17:44:10Z",
          "requester_email_address": "docs@acme.example",
          "decline_message": null,
          "error_message": null,
          "embedded_edit_url": null,
          "embedded_preview_url": null,
          "cfr_part11": false,
          "cfr_part11_signing_mode": null,
          "template_id": null,
          "template_ids": [],
          "metadata": {
            "loan_id": "48213"
          },
          "files": [
            {
              "name": "loan-agreement.pdf",
              "pages_number": 0,
              "status": "processing"
            }
          ],
          "recipients": [
            {
              "id": "signer-1",
              "name": "Jane Doe",
              "email": "jane@example.com",
              "status": "created",
              "signing_order": 1,
              "embedded_signing_url": "https://app.signclad.com/sign/embedded/4f8c1b7a2e9d05364c8b1e7a2d9f06354b8c1e7a2d9f06354b8c1e7a2d9f0635",
              "bounced": false,
              "bounced_details": null
            }
          ],
          "fields": [
            [
              {
                "api_id": "borrower_signature",
                "type": "signature",
                "recipient_id": "signer-1",
                "page": 1,
                "x": 375,
                "y": 206,
                "width": "180.0",
                "height": "44.0",
                "required": true,
                "value": null
              }
            ]
          ],
          "warnings": [
            {
              "code": "lock_sign_date_value_ignored",
              "message": "A value was supplied for borrower_date, which fills itself on signing. It was ignored.",
              "pointer": "/fields/0/1/value"
            }
          ]
        }
      },
      "DocumentSent": {
        "summary": "The same document on a later GET",
        "description": "The settled status, the real page count, and fields regrouped by type rather than left\nin the order they were sent. `width` and `height` come back as decimal strings while\n`x`, `y`, and `page` stay numbers. There is no document-level embedded signing URL.\n",
        "value": {
          "id": "8f14e45f-ea8f-4b2c-9f0e-1b2c3d4e5f60",
          "status": "Sent",
          "archived": false,
          "name": "Loan agreement",
          "subject": "Please sign your loan agreement",
          "message": "<p>Two signatures, about a minute.</p>",
          "test_mode": false,
          "draft": false,
          "embedded_signing": true,
          "apply_signing_order": false,
          "allow_decline": true,
          "allow_reassign": true,
          "language": "en",
          "expires_in": 30,
          "expires_at": "2026-10-09T17:44:10Z",
          "reminders": true,
          "created_at": "2026-09-09T17:44:10Z",
          "updated_at": "2026-09-09T17:44:12Z",
          "completed_at": null,
          "requester_email_address": "docs@acme.example",
          "decline_message": null,
          "error_message": null,
          "embedded_edit_url": null,
          "embedded_preview_url": "https://app.signclad.com/p/8f14e45f-ea8f-4b2c-9f0e-1b2c3d4e5f60",
          "cfr_part11": false,
          "cfr_part11_signing_mode": null,
          "template_id": null,
          "template_ids": [],
          "metadata": {
            "loan_id": "48213"
          },
          "files": [
            {
              "name": "loan-agreement.pdf",
              "pages_number": 4,
              "status": "ready"
            }
          ],
          "recipients": [
            {
              "id": "signer-1",
              "name": "Jane Doe",
              "email": "jane@example.com",
              "status": "sent",
              "signing_order": 1,
              "embedded_signing_url": "https://app.signclad.com/sign/embedded/9d05364c8b1e7a2d9f06354b8c1e7a2d9f06354b8c1e7a2d9f06354b8c1e7a2d",
              "bounced": false,
              "bounced_details": null,
              "viewed_at": null,
              "signed_at": null
            }
          ],
          "fields": [
            [
              {
                "api_id": "borrower_date",
                "type": "date",
                "recipient_id": "signer-1",
                "page": 1,
                "x": 375,
                "y": 262,
                "width": "120.0",
                "height": "24.0",
                "lock_sign_date": true,
                "date_format": "MM/DD/YYYY",
                "value": null
              },
              {
                "api_id": "borrower_signature",
                "type": "signature",
                "recipient_id": "signer-1",
                "page": 1,
                "x": 375,
                "y": 206,
                "width": "180.0",
                "height": "44.0",
                "required": true,
                "value": null
              }
            ]
          ],
          "conditional_logic_decisions": [],
          "completed_pdf_url": null,
          "audit_trail_url": null,
          "sent_revision_hash": "4c9a1f0b7e2d5a836c1b4e9f0a2d7c53e81b6f40a95d2c7e13b8f6a0d4c2e915",
          "source": "api"
        }
      },
      "TemplateCreateDraft": {
        "summary": "Template with one placeholder, opened in the builder",
        "value": {
          "name": "Onboarding agreement",
          "draft": true,
          "text_tags": true,
          "metadata": {
            "s3_keys": "[\"2026/09/09/ws/files/file/originals/onboarding.pdf\"]"
          },
          "files": [
            {
              "name": "onboarding.pdf",
              "file_url": "https://files.example.com/onboarding.pdf?X-Amz-Expires=7200&X-Amz-Signature=..."
            }
          ],
          "placeholders": [
            {
              "id": "borrower",
              "name": "Borrower"
            }
          ],
          "fields": [
            [
              {
                "api_id": "borrower_signature",
                "type": "signature",
                "placeholder_id": "borrower",
                "page": 1,
                "x": 375,
                "y": 206,
                "width": 180,
                "height": 44
              }
            ]
          ]
        }
      },
      "TemplateCreated": {
        "summary": "The 201 from a template create",
        "description": "`Created` is transient. A `GET` reads `Draft` or `Available`, and `PUT {draft: false}`\nis what publishes a draft.\n",
        "value": {
          "id": "3f1c9a2e-6b7d-4c8e-9a0b-1c2d3e4f5a6b",
          "name": "Onboarding agreement",
          "status": "Created",
          "archived": false,
          "created_at": "2026-09-01T12:00:00Z",
          "updated_at": "2026-09-01T12:00:00Z",
          "template_link": "https://app.signclad.com/t/3f1c9a2e6b7d",
          "embedded_edit_url": "https://app.signclad.com/embed/template/9d05364c8b1e7a2d9f06354b8c1e7a2d9f06354b8c1e7a2d9f06354b8c1e7a2d",
          "placeholders": [
            {
              "id": "borrower",
              "name": "Borrower"
            }
          ],
          "files": [
            {
              "name": "onboarding.pdf",
              "pages_number": 0,
              "status": "processing"
            }
          ],
          "fields": [
            [
              {
                "api_id": "borrower_signature",
                "type": "signature",
                "page": 1,
                "placeholder_name": "Borrower",
                "x": 375,
                "y": 206,
                "width": "180.0",
                "height": "44.0",
                "value": ""
              }
            ]
          ]
        }
      },
      "TemplateAvailable": {
        "summary": "A finished template",
        "value": {
          "id": "3f1c9a2e-6b7d-4c8e-9a0b-1c2d3e4f5a6b",
          "name": "Onboarding agreement",
          "status": "Available",
          "archived": false,
          "created_at": "2026-09-01T12:00:00Z",
          "updated_at": "2026-09-08T09:31:44Z",
          "template_link": "https://app.signclad.com/t/3f1c9a2e6b7d",
          "embedded_edit_url": "https://app.signclad.com/embed/template/9d05364c8b1e7a2d9f06354b8c1e7a2d9f06354b8c1e7a2d9f06354b8c1e7a2d",
          "subject": "Please sign your onboarding agreement",
          "message": "<p>Welcome aboard.</p>",
          "expires_in": 30,
          "language": "en",
          "current_revision": 7,
          "placeholders": [
            {
              "id": "borrower",
              "name": "Borrower",
              "signing_order": 1
            }
          ],
          "copied_placeholders": [],
          "files": [
            {
              "name": "onboarding.pdf",
              "pages_number": 4,
              "status": "ready"
            }
          ],
          "fields": [
            [
              {
                "api_id": "borrower_date",
                "type": "date",
                "page": 1,
                "placeholder_name": "Borrower",
                "label": "Date signed",
                "x": 375,
                "y": 262,
                "width": "120.0",
                "height": "24.0",
                "lock_sign_date": true,
                "date_format": "MM/DD/YYYY",
                "value": ""
              },
              {
                "api_id": "borrower_signature",
                "type": "signature",
                "page": 1,
                "placeholder_name": "Borrower",
                "label": "Signature",
                "x": 375,
                "y": 206,
                "width": "180.0",
                "height": "44.0",
                "lock_sign_date": false,
                "value": ""
              }
            ]
          ]
        }
      },
      "TemplateDocumentCreate": {
        "summary": "One template, one recipient, prefilled fields",
        "value": {
          "template_id": "3f1c9a2e-6b7d-4c8e-9a0b-1c2d3e4f5a6b",
          "draft": false,
          "embedded_signing": true,
          "recipients": [
            {
              "id": "signer-1",
              "placeholder_name": "Borrower",
              "name": "Jane Doe",
              "email": "jane@example.com",
              "send_email": false
            }
          ],
          "exclude_placeholders": [
            "Co-borrower"
          ],
          "template_fields": [
            {
              "api_id": "borrower_full_name",
              "value": "Jane Doe"
            },
            {
              "api_id": "borrower_start_date",
              "value": "2026-07-02T00:00:00Z"
            }
          ],
          "conditional_rules_mode": "append",
          "skip_invalid_fields": true,
          "metadata": {
            "loan_id": "48213"
          }
        }
      },
      "FileReplaceDryRun": {
        "summary": "Dry run against a new PDF",
        "value": {
          "name": "loan-agreement-v2.pdf",
          "file_url": "https://files.example.com/loan-agreement-v2.pdf?X-Amz-Expires=7200&X-Amz-Signature=...",
          "strategy": "anchors",
          "scale_to_page": true,
          "dry_run": true
        }
      },
      "FileReplaceReportExample": {
        "summary": "A dry run that needs a look",
        "value": {
          "revision": 7,
          "dry_run": true,
          "requires_review": true,
          "report": {
            "kept": [
              {
                "api_id": "borrower_signature",
                "page": 1,
                "x": 375,
                "y": 206
              }
            ],
            "moved": [
              {
                "api_id": "borrower_date",
                "page": 1,
                "x": 375,
                "y": 289,
                "previous_page": 1,
                "previous_x": 375,
                "previous_y": 262,
                "reason": "Anchor text moved down 27 points."
              }
            ],
            "scaled": [],
            "orphaned": [
              {
                "api_id": "witness_initials",
                "previous_page": 5,
                "reason": "Page 5 does not exist in the new file."
              }
            ]
          },
          "file": {
            "name": "loan-agreement-v2.pdf",
            "pages_number": 4,
            "status": "ready"
          }
        }
      },
      "BulkSendEnqueuing": {
        "summary": "A bulk send that has just started",
        "value": {
          "id": "b1e2c3d4-5a6b-7c8d-9e0f-1a2b3c4d5e6f",
          "name": "Q3 onboarding",
          "status": "Enqueuing",
          "documents_count": 42,
          "documents_completed": 0,
          "documents_not_completed": 42,
          "test_mode": false,
          "templates": [
            {
              "id": "3f1c9a2e-6b7d-4c8e-9a0b-1c2d3e4f5a6b",
              "name": "Onboarding agreement",
              "status": "Available"
            }
          ],
          "created_at": "2026-09-09T17:44:10Z",
          "updated_at": "2026-09-09T17:44:10Z"
        }
      },
      "BulkSendDocumentsPageExample": {
        "summary": "First page of a bulk send's documents",
        "value": {
          "documents": [
            {
              "id": "8f14e45f-ea8f-4b2c-9f0e-1b2c3d4e5f60",
              "status": "Sent",
              "name": "Onboarding agreement",
              "created_at": "2026-09-09T17:44:11Z"
            }
          ],
          "current_page": 1,
          "next_page": 2,
          "previous_page": null,
          "total_count": 42,
          "total_pages": 5
        }
      },
      "ApiApplicationExample": {
        "summary": "An embedded application",
        "value": {
          "id": "5c1d9e7f-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
          "name": "Acme Lending portal",
          "callback_urls": [
            "https://portal.acme.example/signclad/callback"
          ],
          "primary_color": "#1F6FEB",
          "button_text_color": "#FFFFFF",
          "link_text_color": "#1F6FEB",
          "buttons_border_radius": 8,
          "custom_logo_file": "https://files.signclad.com/branding/acme-logo.png",
          "allowed_embed_domains": [
            "portal.acme.example"
          ],
          "owner": {
            "id": "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
            "name": "Bryce Matheson",
            "email": "bryce@acme.example"
          },
          "created_at": "2026-04-02T08:15:00Z",
          "updated_at": "2026-08-20T14:03:22Z"
        }
      },
      "MeExample": {
        "summary": "The membership behind an API key",
        "value": {
          "id": "01K4S7BQ2M3N4P5Q6R7S8T9VWX",
          "role": "owner",
          "archived": false,
          "is_admin": false,
          "user": {
            "id": "01K4S7BQ2MJ8ZCPX5R6T7V8W9Y",
            "name": "Bryce Matheson",
            "first_name": "Bryce",
            "last_name": "Matheson",
            "has_google_registration": false,
            "email": "bryce@acme.example",
            "locale": "en",
            "timezone": "America/Denver"
          },
          "editor_features": {
            "conditional_rules": true,
            "data_validation": true
          },
          "account": {
            "id": "01K4S7BQ2MB3C4D5E6F7G8H9JK",
            "name": "Acme Lending",
            "plan_tier": "business",
            "active_templates": 12,
            "can_create_template": true,
            "can_create_tracking_document": true,
            "can_create_completion_document": true,
            "active_users": [
              {
                "id": "01K4S7BQ2MJ8ZCPX5R6T7V8W9Y",
                "name": "Bryce Matheson",
                "email": "bryce@acme.example",
                "has_google_registration": false,
                "role": "owner"
              }
            ]
          },
          "workspace": {
            "id": "01K4S7BQ2MB3C4D5E6F7G8H9JK",
            "name": "Acme Lending",
            "plan_tier": "business",
            "active_templates": 12,
            "can_create_template": true,
            "can_create_tracking_document": true,
            "can_create_completion_document": true,
            "active_users": [
              {
                "id": "01K4S7BQ2MJ8ZCPX5R6T7V8W9Y",
                "name": "Bryce Matheson",
                "email": "bryce@acme.example",
                "has_google_registration": false,
                "role": "owner"
              }
            ]
          },
          "current_workspace_id": "01K4S7BQ2MD5E6F7G8H9JKMNPQ",
          "contact": {
            "id": "01K4S7BQ2MF7G8H9JKMNPQRSTV",
            "name": "Bryce Matheson",
            "email": "bryce@acme.example",
            "company": "Acme Lending",
            "job_title": "Operations",
            "phone_number": "+14155550142"
          }
        }
      },
      "WebhookEndpointExample": {
        "summary": "A healthy endpoint",
        "value": {
          "id": "01j9k3p2s5b3c4d5e6f7g8h9jk",
          "url": "https://app.example.com/webhooks/signclad",
          "description": "Production receiver",
          "event_types": [
            "document_completed",
            "document_declined",
            "document_error"
          ],
          "events": [
            "document_completed",
            "document_declined",
            "document_error"
          ],
          "api_application_id": null,
          "include_test_mode": false,
          "active": true,
          "status": "active",
          "compatibility": false,
          "secret_last_rotated_at": "2026-08-20T14:03:22+00:00",
          "created_at": "2026-04-02T08:15:00+00:00",
          "updated_at": "2026-09-09T17:44:10+00:00",
          "health": {
            "state": "healthy",
            "consecutive_failures": 0,
            "success_rate_1h": 1,
            "success_rate_24h": 0.998,
            "p95_latency_ms": 210,
            "last_success_at": "2026-09-09T17:43:58+00:00",
            "last_failure_at": "2026-09-08T22:11:04+00:00",
            "last_failure_reason": "502 Bad Gateway",
            "failing_since": null,
            "next_probe_at": null,
            "pending_count": 0
          }
        }
      },
      "WebhookDeliveryExample": {
        "summary": "A delivery that is being retried",
        "value": {
          "next_cursor": "MDFqOWszcDJzNW43cThyMXQydjN3NHg1",
          "has_more": true,
          "data": [
            {
              "id": "01j9k3p2s5n7q8r1t2v3w4x5",
              "webhook_endpoint_id": "01j9k3p2s5b3c4d5e6f7g8h9jk",
              "event_id": "evt_01j9k3p2s5n7q8r1t2v3w4x5",
              "event_type": "document_completed",
              "document_id": "8f14e45f-ea8f-4b2c-9f0e-1b2c3d4e5f60",
              "status": "failed",
              "next_retry_at": "2026-09-09T18:00:00+00:00",
              "delivered_at": null,
              "created_at": "2026-09-09T17:44:15+00:00",
              "request_headers": {
                "X-Signclad-Event": "document_completed",
                "X-Signclad-Event-Id": "evt_01j9k3p2s5n7q8r1t2v3w4x5",
                "X-Signclad-Delivery": "01j9k3p2s5n7q8r1t2v3w4x5",
                "X-Signclad-Attempt": "2",
                "X-Signclad-Signature": "t=1789050255,v1=6f4a9c2e8b1d3f5a7c9e0b2d4f6a8c1e3b5d7f9a0c2e4b6d8f0a2c4e6b8d0f2a"
              },
              "attempts": [
                {
                  "attempt": 1,
                  "attempted_at": "2026-09-09T17:44:15+00:00",
                  "status_code": 502,
                  "latency_ms": 10021,
                  "response_excerpt": "<html>502 Bad Gateway</html>"
                },
                {
                  "attempt": 2,
                  "attempted_at": "2026-09-09T17:45:15+00:00",
                  "status_code": null,
                  "latency_ms": null,
                  "error": "Connection timed out after 10000 ms"
                }
              ]
            }
          ]
        }
      },
      "WebhookDocumentCompleted": {
        "summary": "document_completed",
        "value": {
          "event": {
            "id": "evt_01j9k3p2s5n7q8r1t2v3w4x5",
            "hash": "6f4a9c2e8b1d3f5a7c9e0b2d4f6a8c1e3b5d7f9a0c2e4b6d8f0a2c4e6b8d0f2a",
            "time": 1789050255,
            "type": "document_completed",
            "sequence": 9
          },
          "data": {
            "account_id": "9e0f1a2b-3c4d-5e6f-7a8b-9c0d1e2f3a4b",
            "workspace_id": "4d5e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9a",
            "object": {
              "id": "8f14e45f-ea8f-4b2c-9f0e-1b2c3d4e5f60",
              "status": "Completed",
              "name": "Loan agreement",
              "archived": false,
              "error_message": null,
              "completed_at": "2026-09-09T17:44:15Z",
              "completed_pdf_url": null,
              "audit_trail_url": null,
              "recipients": [
                {
                  "id": "signer-1",
                  "name": "Jane Doe",
                  "email": "jane@example.com",
                  "status": "completed",
                  "signed_at": "2026-09-09T17:44:12Z",
                  "bounced": false,
                  "bounced_details": null
                }
              ],
              "fields": [
                [
                  {
                    "api_id": "borrower_consent",
                    "type": "checkbox",
                    "recipient_id": "signer-1",
                    "page": 1,
                    "x": 100,
                    "y": 300,
                    "width": "20.0",
                    "height": "20.0",
                    "value": "t"
                  }
                ]
              ]
            }
          }
        }
      },
      "WebhookTemplateCreated": {
        "summary": "template_created",
        "value": {
          "event": {
            "id": "evt_01J9K3P2S5N7Q8R1T2V3W4X6",
            "hash": "9a0c2e4b6d8f0a2c4e6b8d0f2a6f4a9c2e8b1d3f5a7c9e0b2d4f6a8c1e3b5d7f",
            "time": 1789050100,
            "type": "template_created",
            "sequence": 1
          },
          "data": {
            "account_id": "9e0f1a2b-3c4d-5e6f-7a8b-9c0d1e2f3a4b",
            "workspace_id": "4d5e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9a",
            "object": {
              "id": "3f1c9a2e-6b7d-4c8e-9a0b-1c2d3e4f5a6b",
              "name": "Onboarding agreement",
              "status": "Available",
              "archived": false
            }
          }
        }
      },
      "EmbedContextExample": {
        "summary": "A document builder session with the compatibility shim on",
        "value": {
          "view": "document_builder",
          "document_id": "8f14e45f-ea8f-4b2c-9f0e-1b2c3d4e5f60",
          "template_id": null,
          "scope": "document_edit",
          "api_app_styles": {
            "primary_color": "#1F6FEB",
            "button_text_color": "#FFFFFF",
            "link_text_color": "#1F6FEB",
            "buttons_border_radius": 8,
            "logo_url": "https://files.signclad.com/branding/acme-logo.png?expires=1789050600&signature=..."
          },
          "allowed_parent_origins": [
            "https://portal.acme.example"
          ],
          "iframe_flags": [
            "signclad_embedded_iframe"
          ],
          "embed_globals": [
            "SigncladEmbed"
          ],
          "compat": {
            "messages": {
              "outbound": {
                "SC-document.setId": [
                  "SC-document.setId",
                  "SW-document.setId"
                ],
                "SC-document.completed": [
                  "SC-document.completed",
                  "SW-document.completed"
                ]
              },
              "inbound": {
                "SC-Embedder-SetAttr": [
                  "SC-Embedder-SetAttr",
                  "SW-Embedder-SetAttr"
                ]
              }
            }
          },
          "new_edit_url": "https://app.signclad.com/embed/document/4f8c1b7a2e9d05364c8b1e7a2d9f06354b8c1e7a2d9f06354b8c1e7a2d9f0635",
          "session": {
            "expires_at": "2026-09-09T18:14:10Z"
          }
        }
      },
      "SessionPayloadExample": {
        "summary": "Signed in, no second factor needed",
        "value": {
          "ok": true,
          "user": {
            "id": "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
            "name": "Bryce Matheson",
            "email": "bryce@acme.example",
            "email_verified": true
          },
          "mfa": {
            "required": true,
            "enrolled": true,
            "methods": [
              "passkey",
              "totp",
              "recovery"
            ],
            "enforced_from": "2026-10-01T00:00:00Z",
            "days_remaining": null,
            "blocked": false
          }
        }
      },
      "MfaChallengeExample": {
        "summary": "A second factor is needed",
        "description": "Still a 200. Answer it at POST /login/mfa.",
        "value": {
          "mfa_required": true,
          "methods": [
            "passkey",
            "totp"
          ],
          "passkey_options": {
            "challenge": "9c8f2b1a4d6e7f80a1b2c3d4e5f60718",
            "rpId": "signclad.com",
            "userVerification": "required",
            "timeout": 60000,
            "allowCredentials": [
              {
                "type": "public-key",
                "id": "AQIDBAUGBwgJCgsMDQ4PEA",
                "transports": [
                  "internal",
                  "hybrid"
                ]
              }
            ]
          }
        }
      }
    }
  }
}
