{
  "openapi": "3.1.0",
  "info": {
    "title": "SignSealer",
    "version": "1.0.0",
    "description": "Signatures with the evidence behind them.\n\nEvery write that creates something takes an optional `Idempotency-Key`, with Stripe's semantics — the same key and body replays, a different body is refused. Every response carries an `x-request-id`; quote it if you need us to look something up.",
    "contact": {
      "name": "SignSealer",
      "url": "https://signsealer.com"
    }
  },
  "servers": [
    {
      "url": "https://api.signsealer.com"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key, as `Authorization: Bearer mk_live_…`. Keys are shown once when issued and stored only as their hash. A key is a member of the account: its scopes are its capabilities, and revoking it ends its access in the same statement."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "hint": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Account",
      "description": "Whose key this is."
    },
    {
      "name": "Templates",
      "description": "The text people sign, and its versions."
    },
    {
      "name": "Documents",
      "description": "One agreement, its signers and its evidence."
    },
    {
      "name": "Workflows",
      "description": "A template plus the rules for running it."
    },
    {
      "name": "Subjects",
      "description": "Reservations and activities, and what each needs signed."
    },
    {
      "name": "Packets",
      "description": "Several documents about one subject, behind one link."
    },
    {
      "name": "Webhooks",
      "description": "Where completions are delivered."
    },
    {
      "name": "Signing",
      "description": "What a person holding a signing link can do. No API key: the token is the credential, and attaching a key would let any member of any tenant sign anything whose token they could guess."
    },
    {
      "name": "Verify",
      "description": "Checking a certificate from its printed code, without an account."
    }
  ],
  "paths": {
    "/v1/account": {
      "get": {
        "operationId": "get-account",
        "summary": "Show the account this key belongs to",
        "description": "The first call any integration makes: prove the credential works and show the operator the name of the account they just connected.",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:read",
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "tenant_id": "3a1f…",
                  "name": "Fern Hollow Stables",
                  "slug": "fern-hollow",
                  "key_label": "Booking system",
                  "scopes": [
                    "signing:read",
                    "signing:write"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/branding": {
      "get": {
        "operationId": "get-branding",
        "summary": "The brand the account shows its signers",
        "description": "The business's name, colours, whether it has logos on file (fetch them at /brand/{tenant_id}/logo.png and logo-dark.png) and the host its signing links use. SignSealer's own until the business has the white-label add-on and has set its brand.",
        "tags": [
          "Other"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:read",
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "businessName": "Fern Hollow Stables",
                  "whiteLabelEnabled": true,
                  "primaryColor": "#2f5d3a",
                  "accentColor": "#c98b2b",
                  "backgroundColor": "#f6f4ee",
                  "textColor": "#1f2a22",
                  "buttonTextColor": "#ffffff",
                  "hasLogo": true,
                  "hasDarkLogo": true,
                  "hasOpening": true,
                  "openingType": "image/jpeg",
                  "openingUpdatedAt": "2026-10-03T14:22:09Z",
                  "signingHost": "fern-hollow.signsealer.com",
                  "footerText": "Questions? Call the barn.",
                  "tenantId": "3a1f…",
                  "logoUrl": "https://api.signsealer.com/brand/3a1f…/logo.png",
                  "logoDarkUrl": "https://api.signsealer.com/brand/3a1f…/logo-dark.png",
                  "openingUrl": "https://api.signsealer.com/brand/3a1f…/opening.jpg?v=1789000000"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/sms-consents": {
      "post": {
        "operationId": "post-sms-consents",
        "summary": "Record consent to text a number",
        "description": "For a number you took somewhere SignSealer is not: your booking form, an order, the counter. Send the exact wording that was in front of the person, because that is the record a carrier asks for and it is kept verbatim. It has to say that replying STOP opts out, that HELP gets help, that message and data rates may apply, and that message frequency varies; a disclosure missing any of those is refused with 422 naming which, and nothing is recorded. One call covers the link, the reminder and the confirmation, because somebody who agreed to those agreed to all three. A number that has replied STOP cannot be re-consented by you — they have to opt in again themselves.",
        "tags": [
          "Other"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Your own key, 8–255 characters. The same key with the same body replays the first response without doing the work again; the same key with a different body is refused rather than absorbed.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "phone": "+15035550142",
                  "purposes": [
                    "signing.link",
                    "signing.reminder",
                    "signing.completed"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "phone": {
                    "type": "string",
                    "minLength": 7,
                    "maxLength": 20
                  },
                  "obtained_via": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 200
                  },
                  "wording": {
                    "type": "string",
                    "minLength": 20,
                    "maxLength": 2000
                  }
                },
                "required": [
                  "phone",
                  "obtained_via",
                  "wording"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      },
      "get": {
        "operationId": "get-sms-consents",
        "summary": "Whether a number may be texted",
        "description": "Pass `phone`. Answers for that one number — so an integration can decide whether to put the tick box in front of somebody again. It deliberately does not list every number the account holds. `may_text` is what the engine will actually do rather than what one of its checks says: consent has to be live *and* the number must not be suppressed, because a number that hard-bounced keeps its consent and still cannot be reached. When it is false, `why_not` says which of `revoked`, `suppressed` or `no_consent`.",
        "tags": [
          "Other"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:read",
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "phone": "+15035550142",
                  "may_text": true,
                  "why_not": null,
                  "obtained_via": "booking form, checkbox beside the phone field",
                  "obtained_at": "2026-10-03T14:22:09Z"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/templates": {
      "get": {
        "operationId": "get-templates",
        "summary": "List templates",
        "description": "Every version of every template, newest first, with whether each is published and how many documents were prepared from it.",
        "tags": [
          "Templates"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:read",
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "templates": [
                    {
                      "template_id": "8c2e…",
                      "code": "liability-waiver",
                      "name": "Liability waiver",
                      "kind": "waiver",
                      "version": 2,
                      "is_published": true,
                      "published_at": "2026-10-03T14:22:09Z",
                      "variables": [
                        "participant_name",
                        "date",
                        "emergency_contact"
                      ],
                      "grants": {},
                      "documents": 412,
                      "is_latest": true
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "post-templates",
        "summary": "Create a template",
        "description": "Creates version 1, as a draft. A template is not usable until it is published, and publishing is what freezes the text people will have signed. `fields` says what the document asks for besides a signature — a date of birth, an address, one of a list — each `required` unless it says otherwise, and each checked when a signer supplies it. Marking one `sensitive` masks the answer wherever staff read it back; it is not masked on the sealed PDF, because the PDF is the document that was signed.",
        "tags": [
          "Templates"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:templates",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Your own key, 8–255 characters. The same key with the same body replays the first response without doing the work again; the same key with a different body is refused rather than absorbed.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "template_id": "8c2e…"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "pattern": "^[a-z][a-z0-9_-]*$"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "nda",
                      "waiver",
                      "contract",
                      "consent",
                      "policy",
                      "other"
                    ]
                  },
                  "body": {
                    "type": "string",
                    "minLength": 1
                  },
                  "variables": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "grants": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "consent_text": {
                    "nullable": true,
                    "type": "string"
                  },
                  "fields": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string",
                          "pattern": "^[a-z][a-z0-9_]{0,58}$"
                        },
                        "label": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200
                        },
                        "kind": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 20
                        },
                        "required": {
                          "type": "boolean"
                        },
                        "sensitive": {
                          "type": "boolean"
                        },
                        "supplied_by": {
                          "type": "string",
                          "enum": [
                            "adult",
                            "per_child",
                            "staff"
                          ]
                        },
                        "options": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 80
                          },
                          "maxItems": 30
                        },
                        "hint": {
                          "type": "string",
                          "maxLength": 200
                        }
                      },
                      "required": [
                        "key",
                        "kind"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 50
                  }
                },
                "required": [
                  "code",
                  "name",
                  "kind",
                  "body"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/templates/{id}/pdf": {
      "get": {
        "operationId": "get-templates-by-id-pdf",
        "summary": "Whether a template is an uploaded PDF",
        "description": "`has_pdf` says whether the template is a PDF signed where it is rather than text. If it is: `page_count` is what the file has, `drawn` is how many pages have a picture a signer can read, and `ready` is whether those agree — a PDF template cannot be published until they do. `sha256` is the fingerprint of the file itself, which is the fingerprint the template's body names and the certificate commits to. Uploading the file is a dashboard step; see /docs/pdf.",
        "tags": [
          "Templates"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "has_pdf": true,
                  "page_count": 4,
                  "drawn": 4,
                  "ready": true,
                  "pages": [
                    {
                      "n": 1,
                      "width": 612,
                      "height": 792,
                      "rotation": 0
                    }
                  ],
                  "bytes": 284113,
                  "sha256": "e362cc76dae17bbe2a9fc551d9b795fa4e0f77fb488eb748378a125b11f36324",
                  "original_name": "tenancy.pdf",
                  "uploaded_at": "2026-10-03T14:22:09Z"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/documents": {
      "get": {
        "operationId": "get-documents",
        "summary": "List documents",
        "description": "Filter with `status` (a state, or `open` for draft and sent) and `q` (title, subject or signer). Page with `limit` (1–200, 50 by default; more than 200 gets 200) and `offset` (0 by default). Either one that is not a whole number is a 400 saying which.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:read",
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "documents": [
                    {
                      "document_id": "d41c…",
                      "title": "Liability waiver — Dana Reyes",
                      "status": "sent",
                      "template_code": "liability-waiver",
                      "template_version": 2,
                      "subject_name": null,
                      "signers": 2,
                      "signed": 1,
                      "waiting_on": "Sam Manager",
                      "sequential": true,
                      "expires_at": "2026-10-17T00:00:00Z",
                      "sent_at": "2026-10-03T14:22:09Z",
                      "completed_at": null,
                      "void_reason": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "post-documents",
        "summary": "Prepare a document from a template",
        "description": "Renders the published template with the values given and stores the result on the document. Every declared variable needs a value: a contract that ships with a placeholder still in it is worse than one that failed to send.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Your own key, 8–255 characters. The same key with the same body replays the first response without doing the work again; the same key with a different body is refused rather than absorbed.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "document_id": "d41c…"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "template_code": {
                    "type": "string"
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "values": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "expires_at": {
                    "nullable": true,
                    "type": "string",
                    "format": "date-time"
                  },
                  "sequential": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "template_code"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/documents/{id}": {
      "get": {
        "operationId": "get-documents-by-id",
        "summary": "Show one document",
        "description": "The document, its signers, and the whole event trail with the text hash each event was made against.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "document_id": "d41c…",
                  "title": "Liability waiver — Dana Reyes",
                  "status": "completed",
                  "body": "I, Dana Reyes, …",
                  "body_sha256": "e362cc76dae17bbe2a9fc551d9b795fa4e0f77fb488eb748378a125b11f36324",
                  "template_code": "liability-waiver",
                  "template_version": 2,
                  "grants": {},
                  "sequential": false,
                  "sent_at": "2026-10-03T14:22:09Z",
                  "completed_at": "2026-10-03T14:22:09Z",
                  "voided_at": null,
                  "void_reason": null,
                  "integrity_ok": true,
                  "signers": [
                    {
                      "signer_id": "5f0a…",
                      "email": "dana@example.com",
                      "full_name": "Dana Reyes",
                      "role": "signer",
                      "sign_order": 1,
                      "status": "signed",
                      "party_id": null,
                      "consented_at": "2026-10-03T14:22:09Z",
                      "signed_at": "2026-10-03T14:22:09Z",
                      "declined_at": null,
                      "decline_reason": null,
                      "token_expires_at": "2026-10-17T00:00:00Z"
                    }
                  ],
                  "events": [
                    {
                      "event_id": "e1…",
                      "kind": "sent",
                      "occurred_at": "2026-10-03T14:22:09Z",
                      "signer_email": null,
                      "ip": null,
                      "user_agent": null,
                      "against_sha256": "e362cc76dae17bbe2a9fc551d9b795fa4e0f77fb488eb748378a125b11f36324",
                      "detail": null
                    },
                    {
                      "event_id": "e2…",
                      "kind": "consented",
                      "occurred_at": "2026-10-03T14:22:09Z",
                      "signer_email": "dana@example.com",
                      "ip": "203.0.113.9",
                      "user_agent": "Mozilla/5.0 …",
                      "against_sha256": "e362cc76dae17bbe2a9fc551d9b795fa4e0f77fb488eb748378a125b11f36324",
                      "detail": null
                    },
                    {
                      "event_id": "e3…",
                      "kind": "signed",
                      "occurred_at": "2026-10-03T14:22:09Z",
                      "signer_email": "dana@example.com",
                      "ip": "203.0.113.9",
                      "user_agent": "Mozilla/5.0 …",
                      "signature_method": "typed",
                      "against_sha256": "e362cc76dae17bbe2a9fc551d9b795fa4e0f77fb488eb748378a125b11f36324",
                      "detail": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/documents/{id}/signers": {
      "get": {
        "operationId": "get-documents-by-id-signers",
        "summary": "Who has signed one document",
        "description": "The gate's question (00340): each signer's name, role and state, and nothing else -- no text, no addresses, no event trail. The route a paired tablet reads; the full document is `GET /v1/documents/:id`.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "document_id": "d41c…",
                  "title": "Liability waiver — Dana Reyes",
                  "status": "sent",
                  "sent_at": "2026-10-03T14:22:09Z",
                  "completed_at": null,
                  "signers": [
                    {
                      "signer_id": "5f0a…",
                      "full_name": "Dana Reyes",
                      "role": "signer",
                      "sign_order": 1,
                      "status": "signed",
                      "signed_at": "2026-10-03T14:22:09Z",
                      "declined_at": null
                    },
                    {
                      "signer_id": "6a1b…",
                      "full_name": "Sam Manager",
                      "role": "signer",
                      "sign_order": 2,
                      "status": "pending",
                      "signed_at": null,
                      "declined_at": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/documents/{id}/signers/{signer}/link": {
      "post": {
        "operationId": "post-documents-by-id-signers-by-signer-link",
        "summary": "A fresh signing link for a signer who has not signed, as a QR code",
        "description": "The last check at the gate (00340). Mints a new link for the signer -- the one they were sent stops working -- and returns it with an SVG QR code to hold up, so a customer who never signed at the counter scans it and signs on their own phone. Recorded on the trail as `link_shown`. Refused for a signer who has signed or declined, a copied recipient, or a document that is not open; ten a minute per document.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:write",
        "x-or-scope": "signing:workflows",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "signer",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "document_id": "d41c…",
                  "signer_id": "5f0a…",
                  "url": "https://api.signsealer.com/s/9k3…",
                  "qr_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" …>…</svg>"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/documents/{id}/send": {
      "post": {
        "operationId": "post-documents-by-id-send",
        "summary": "Send a document for signature",
        "description": "Returns one signing link per signer, once. The tokens are not stored, only their hashes, so a lost link is replaced by a reminder rather than looked up.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Your own key, 8–255 characters. The same key with the same body replays the first response without doing the work again; the same key with a different body is refused rather than absorbed.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "links": [
                    {
                      "signer_id": "5f0a…",
                      "email": "dana@example.com",
                      "phone": "+16155550142",
                      "token": "3f9c…64 hex characters"
                    },
                    {
                      "signer_id": "7b21…",
                      "email": null,
                      "phone": "+16155550188",
                      "token": "a08e…64 hex characters"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "signers": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "phone": {
                          "type": "string",
                          "minLength": 7,
                          "maxLength": 40
                        },
                        "full_name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200
                        },
                        "role": {
                          "type": "string",
                          "enum": [
                            "signer",
                            "approver",
                            "witness",
                            "cc"
                          ]
                        },
                        "sign_order": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 50
                        },
                        "party_id": {
                          "nullable": true,
                          "type": "string",
                          "format": "uuid"
                        }
                      },
                      "required": [
                        "full_name"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 50
                  }
                },
                "required": [
                  "signers"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/documents/{id}/void": {
      "post": {
        "operationId": "post-documents-by-id-void",
        "summary": "Void a document",
        "description": "Needs a reason, which goes on the audit trail. An executed agreement cannot be voided: it is terminated by agreement, not by deletion.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Your own key, 8–255 characters. The same key with the same body replays the first response without doing the work again; the same key with a different body is refused rather than absorbed.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "voided": true
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/documents/{id}/certificate": {
      "get": {
        "operationId": "get-documents-by-id-certificate",
        "summary": "Fetch the certificate of completion",
        "description": "Issued when the document completes, frozen at issue and hashed. `still_matches_record` re-derives it from the live record and reports any divergence rather than correcting it.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "certificate_id": "c7d0…",
                  "verification_code": "K7QP3MTV9XBD2FHR",
                  "issued_at": "2026-10-03T14:22:09Z",
                  "document_sha256_hex": "e362cc76dae17bbe2a9fc551d9b795fa4e0f77fb488eb748378a125b11f36324",
                  "certificate_sha256_hex": "592f…",
                  "still_matches_record": true,
                  "payload": {
                    "issuer": {
                      "tenant": "Fern Hollow Stables",
                      "tenant_id": "3a1f…"
                    },
                    "document": {
                      "id": "d41c…",
                      "title": "Liability waiver — Dana Reyes",
                      "sha256": "e362cc76dae17bbe2a9fc551d9b795fa4e0f77fb488eb748378a125b11f36324",
                      "completed_at": "2026-10-03T14:22:09Z",
                      "template": {
                        "code": "liability-waiver",
                        "version": 2
                      }
                    },
                    "signers": [
                      {
                        "full_name": "Dana Reyes",
                        "email": "dana@example.com",
                        "role": "signer",
                        "sign_order": 1,
                        "status": "signed",
                        "consented_at": "2026-10-03T14:22:09Z",
                        "signed_at": "2026-10-03T14:22:09Z",
                        "signature": {
                          "method": "typed",
                          "value_sha256": "017a…",
                          "signed_against_sha256": "e362cc76dae17bbe2a9fc551d9b795fa4e0f77fb488eb748378a125b11f36324",
                          "ip": "203.0.113.9"
                        }
                      }
                    ],
                    "events": [
                      {
                        "kind": "signed",
                        "occurred_at": "2026-10-03T14:22:09Z",
                        "signer": "dana@example.com",
                        "ip": "203.0.113.9",
                        "against_sha256": "e362cc76dae17bbe2a9fc551d9b795fa4e0f77fb488eb748378a125b11f36324"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/documents/{id}/pdf": {
      "get": {
        "operationId": "get-documents-by-id-pdf",
        "summary": "Download the sealed PDF",
        "description": "The certificate as a PAdES-sealed PDF, with an RFC 3161 timestamp over the signature. Returns base64 bytes and the filename to save them under.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "filename": "certificate-K7QP3MTV9XBD2FHR.pdf",
                  "content_type": "application/pdf",
                  "seal": {
                    "commonName": "SignSealer",
                    "selfIssued": true,
                    "trust": "unverified",
                    "timestamped": true
                  },
                  "bytes_base64": "JVBERi0xLjcK…"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/documents/{id}/bundle": {
      "get": {
        "operationId": "get-documents-by-id-bundle",
        "summary": "Download the evidence bundle",
        "description": "The sealed PDF with the evidence package printed after the audit trail: every message sent about the document, every webhook delivered, and the record's state when the file was made, with the package's SHA-256 on the last page. One PAdES-sealed file. Returns base64 bytes and the filename to save them under.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "filename": "evidence-bundle-K7QP3MTV9XBD2FHR.pdf",
                  "content_type": "application/pdf",
                  "seal": {
                    "commonName": "SignSealer",
                    "selfIssued": true,
                    "trust": "unverified",
                    "timestamped": true
                  },
                  "bytes_base64": "JVBERi0xLjcK…"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/workflows": {
      "get": {
        "operationId": "get-workflows",
        "summary": "List workflows",
        "description": "A workflow is a published template plus the rules for running it: who signs in what order, how long a link lives, and whether it has a public address.",
        "tags": [
          "Workflows"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:read",
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "workflows": [
                    {
                      "id": "w9b1…",
                      "code": "waiver",
                      "name": "Kayak rental waiver",
                      "kind": "waiver",
                      "template_code": "liability-waiver",
                      "template_published": true,
                      "signer_roles": [
                        {
                          "label": "Participant",
                          "role": "signer",
                          "order": 1,
                          "participant": true
                        }
                      ],
                      "sequential": false,
                      "countersign": false,
                      "expires_days": 7,
                      "remind_after_days": 3,
                      "public_slug": "waiver",
                      "intro": "Read it, answer two questions, sign.",
                      "form_fields": [
                        {
                          "key": "emergency_contact",
                          "label": "Emergency contact",
                          "required": true
                        }
                      ],
                      "active": true,
                      "drafted_by_run": null,
                      "created_at": "2026-10-03T14:22:09Z",
                      "updated_at": "2026-10-03T14:22:09Z",
                      "started": 412,
                      "completed": 398
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/workflows/{id}/qr": {
      "get": {
        "operationId": "get-workflows-by-id-qr",
        "summary": "The QR code for a workflow's public form",
        "description": "The public address of a workflow, drawn as a QR code: an SVG to put on a page and a PNG (1024px, base64) to print. Anybody who scans it opens the form on their own phone and signs there. 404 for a workflow with no public address.",
        "tags": [
          "Workflows"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "workflow_id": "w9b1…",
                  "name": "Kayak rental waiver",
                  "url": "https://signsealer.com/f/harbour-rentals/waiver",
                  "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 33 33\" …>…</svg>",
                  "png_base64": "iVBORw0KGgo…"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/workflows/{id}/start": {
      "post": {
        "operationId": "post-workflows-by-id-start",
        "summary": "Start a workflow for one participant",
        "description": "Prepares the document, sends it to the participant and to every fixed role, and returns the tokens once. A tablet reconciling a start it ran with no network names `expected_template_version` (refused with 409 `template_changed` if the published form has moved on, so nothing is sealed against words nobody read) and `captured` (its own clock, kept as a claim with the skew computed); it sends the `date` it showed under `values` so the frozen text carries the day the customer was there. `return_url` is where the signed page sends the person afterwards: https, on a domain this business has proven (a verified signing domain, a proven company sign-in domain, or the site of a connected app), else 422.",
        "tags": [
          "Workflows"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:workflows",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Your own key, 8–255 characters. The same key with the same body replays the first response without doing the work again; the same key with a different body is refused rather than absorbed.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "document_id": "d41c…",
                  "title": "Kayak rental waiver — Dana Reyes",
                  "expires_at": "2026-10-10T14:22:09Z",
                  "signers": [
                    {
                      "signer_id": "5f0a…",
                      "email": "dana@example.com",
                      "full_name": "Dana Reyes",
                      "role": "signer",
                      "participant": true,
                      "token": "3f9c…"
                    },
                    {
                      "signer_id": "5f0b…",
                      "email": "manager@fernhollow.example",
                      "full_name": "Sam Manager",
                      "role": "signer",
                      "participant": false,
                      "token": "7a21…"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "full_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "fields": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "values": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "external_ref": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "expected_template_version": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "captured": {
                    "type": "object",
                    "properties": {
                      "offline": {
                        "type": "boolean"
                      },
                      "at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "device": {
                        "type": "string",
                        "maxLength": 120
                      }
                    },
                    "required": [
                      "offline",
                      "at",
                      "device"
                    ],
                    "additionalProperties": false
                  },
                  "return_url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000
                  }
                },
                "required": [
                  "email",
                  "full_name"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/workflows/{id}/bundle": {
      "get": {
        "operationId": "get-workflows-by-id-bundle",
        "summary": "Everything needed to run a workflow offline",
        "description": "The workflow's fields and roles, the published template's text, variables and version, the consent and intent wording a signer is shown with their versions, the business's name, and the pages of an uploaded PDF (fetch each at `/v1/workflows/{id}/pages/{n}`). A tablet keeps this so it can start a signing with no network, then reconciles the start naming the version it rendered.",
        "tags": [
          "Workflows"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:workflows",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "workflow": {
                    "id": "w9b1…",
                    "code": "waiver",
                    "name": "Kayak rental waiver",
                    "kind": "kiosk",
                    "intro": "Please read and sign before launch.",
                    "form_fields": [
                      {
                        "key": "boat",
                        "label": "Boat",
                        "type": "text",
                        "required": true
                      }
                    ],
                    "signer_roles": [
                      {
                        "role": "signer",
                        "participant": true,
                        "order": 1
                      }
                    ],
                    "sequential": false,
                    "countersign": false
                  },
                  "template": {
                    "id": "t7d2…",
                    "code": "kayak-waiver",
                    "name": "Kayak rental waiver",
                    "kind": "waiver",
                    "version": 3,
                    "variables": [
                      "participant_name",
                      "date",
                      "boat"
                    ],
                    "body": "Between {{business_name}} and {{participant_name}}, on {{date}}. Boat: {{boat}}. …",
                    "body_sha256": "9c1e…"
                  },
                  "consent": {
                    "text": "I agree to sign this document electronically. …",
                    "version": "esign/v1+template/t7d2…/v3"
                  },
                  "intent": {
                    "text": "I intend this signature to be my legally binding signature.",
                    "version": "intent/v1"
                  },
                  "business_name": "Harbour Rentals",
                  "pages": [],
                  "fetched_at": "2026-09-16T09:48:15Z"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/workflows/{id}/pages/{n}": {
      "get": {
        "operationId": "get-workflows-by-id-pages-by-n",
        "summary": "One page of a workflow's PDF template, as an image",
        "description": "The published template's page `n` as a PNG, for a tablet keeping the form on the device: base64 bytes and the content type, like the PDF routes. 404 for a text template or a page it does not have. A published template's pages do not change, so the tablet may keep the file.",
        "tags": [
          "Workflows"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:workflows",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "n",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "page": 1,
                  "content_type": "image/png",
                  "bytes_base64": "iVBORw0KGgo…"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/devices/pair": {
      "post": {
        "operationId": "post-devices-pair",
        "summary": "Trade a pairing code for a device credential",
        "description": "The code is shown once on the fleet screen, lives fifteen minutes and is spent on first use. What comes back is an API key scoped to this device alone and the workflow it has been given, if it has one. Every refusal answers identically — a code that is wrong, expired, spent or belongs to a stopped device all read the same, because telling them apart tells a guesser whether they are warm.",
        "tags": [
          "Other"
        ],
        "security": [],
        "x-scope": "none",
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "device_id": "de91…",
                  "name": "Front counter iPad",
                  "key": "mk_live_9f2a…",
                  "workflow_id": "wf3c…",
                  "workflow_name": "River float waiver"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 32
                  },
                  "platform": {
                    "type": "string",
                    "enum": [
                      "ios",
                      "android",
                      "other"
                    ]
                  },
                  "app_version": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "os_version": {
                    "type": "string",
                    "maxLength": 40
                  }
                },
                "required": [
                  "code"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/devices/heartbeat": {
      "post": {
        "operationId": "post-devices-heartbeat",
        "summary": "Say the device is still here, and read back what it should be running",
        "description": "The device does not say which device it is — the credential it called with does. So a tablet cannot report on another's behalf, and a heartbeat from a revoked credential resolves to no device at all. The answer carries the device's name and assigned workflow, so a change made on the fleet screen reaches the tablet without re-pairing; and `update`: the newest Android build available with where to get it, and where this tablet stands against the version rules set in Ops (`required` below the minimum, `recommended_now` below the recommended version). A business on the preview list is offered the preview build, when one newer than the shipped build exists, and `channel` says `preview`.",
        "tags": [
          "Other"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:device",
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "device_id": "de91…",
                  "name": "Front counter iPad",
                  "platform": "android",
                  "workflow_id": "wf3c…",
                  "workflow_name": "River float waiver",
                  "latest_android_version": "1.2.0",
                  "download_url": "https://app.signsealer.com/app/devices",
                  "update": {
                    "latest": "1.2.0",
                    "channel": "stable",
                    "download_url": "https://app.signsealer.com/app/devices",
                    "minimum": "1.1.0",
                    "recommended": "1.2.0",
                    "required": false,
                    "recommended_now": true,
                    "announce": true,
                    "preview": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "app_version": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "os_version": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "queue_waiting": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100000
                  },
                  "queue_refused": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100000
                  },
                  "synced": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/devices/report": {
      "post": {
        "operationId": "post-devices-report",
        "summary": "Report a problem from a tablet",
        "description": "Names and numbers, never text (00341): a `code` such as `bundle_page_missing`, the `screen` and `step` the tablet was on, whether it was `online`, how many items were waiting to send, and the HTTP status if a request was refused. There is no field for a message. Kept against the tablet for ninety days, shown on the fleet screens, and forwarded to error reporting with the same fields and no others. Thirty an hour per tablet.",
        "tags": [
          "Other"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:device",
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "report_id": "r7a1…",
                  "device_id": "de91…"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "pattern": "^[a-z][a-z0-9_]{1,39}$"
                  },
                  "screen": {
                    "type": "string",
                    "pattern": "^[a-z][a-z0-9_/-]{0,39}$"
                  },
                  "step": {
                    "type": "string",
                    "pattern": "^[a-z][a-z0-9_]{0,39}$"
                  },
                  "online": {
                    "type": "boolean"
                  },
                  "queue_waiting": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100000
                  },
                  "http_status": {
                    "type": "integer",
                    "minimum": 100,
                    "maximum": 599
                  },
                  "app_version": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "os_version": {
                    "type": "string",
                    "maxLength": 40
                  }
                },
                "required": [
                  "code"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/devices": {
      "get": {
        "operationId": "get-devices",
        "summary": "List tablets",
        "description": "Every tablet of the account: paired or waiting, what it runs, when it was last seen, and whether a pairing code is outstanding. Never the code itself, which is shown once when it is made.",
        "tags": [
          "Other"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:devices",
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "devices": [
                    {
                      "id": "de91…",
                      "name": "Front counter iPad",
                      "workflow_id": "wf3c…",
                      "workflow_name": "River float waiver",
                      "paired": true,
                      "paired_at": "2026-09-02T15:04:11Z",
                      "platform": "android",
                      "app_version": "1.0.3",
                      "os_version": "14",
                      "last_seen_at": "2026-09-16T09:40:02Z",
                      "revoked_at": null,
                      "revoke_reason": null,
                      "pairing_open": false,
                      "pair_expires_at": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "post-devices",
        "summary": "Add a tablet and get its pairing code",
        "description": "Returns the pairing code once and never again: eight characters, fifteen minutes, one use. Type it into the SignSealer app on the tablet, which trades it for a credential of its own. `workflow_id` locks the tablet to one form (a kiosk); leave it out for a staff tablet that chooses. `for_review` makes a code that lasts thirty days, for an app store reviewer.",
        "tags": [
          "Other"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:devices",
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "id": "de92…",
                  "code": "K7PQ2M9D",
                  "expires_at": "2026-09-16T10:03:15Z",
                  "review": false
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60
                  },
                  "workflow_id": {
                    "nullable": true,
                    "type": "string",
                    "format": "uuid"
                  },
                  "for_review": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/devices/{id}/code": {
      "post": {
        "operationId": "post-devices-by-id-code",
        "summary": "A new pairing code for a tablet",
        "description": "For a tablet that never paired, or one being set up again on new hardware. Any tablet paired to this one stops working the moment the new code is made, so a lost tablet is one call here rather than a new key for everything else.",
        "tags": [
          "Other"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:devices",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "id": "de92…",
                  "code": "R4WX8NHB",
                  "expires_at": "2026-09-16T10:05:40Z",
                  "review": false
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/devices/{id}": {
      "patch": {
        "operationId": "patch-devices-by-id",
        "summary": "Rename a tablet, change what it runs, or how it is handed a customer",
        "description": "Any of the fields. A change reaches a paired tablet on its next heartbeat, without re-pairing. `workflow_id: null` makes it a staff tablet that chooses. `hand_off` (00321, 00343) is `walk_up` (a person types their name), `same_device` (your till app hands the signing code to ours on the same tablet) or `separate` (a separate till; the tablet lists who is waiting).",
        "tags": [
          "Other"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:devices",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "updated": true
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60
                  },
                  "workflow_id": {
                    "nullable": true,
                    "type": "string",
                    "format": "uuid"
                  },
                  "hand_off": {
                    "type": "string",
                    "enum": [
                      "walk_up",
                      "same_device",
                      "separate"
                    ]
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "delete-devices-by-id",
        "summary": "Stop a tablet",
        "description": "Its credential stops in the same breath. Needs `?reason=`, which goes on the audit trail beside who stopped it and when.",
        "tags": [
          "Other"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:devices",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "revoked": true
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/subjects": {
      "put": {
        "operationId": "put-subjects",
        "summary": "Import a reservation or activity, and build its packet",
        "description": "Keyed on (provider, external_id), so importing the same reservation twice is one reservation, which is why this needs no idempotency key. Participants and the packet are built in the same call, because a caller who imported a reservation always wants to know what is now required.",
        "tags": [
          "Subjects"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:subjects",
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "subject_id": "s2e4…",
                  "packet": {
                    "packet_id": "p6c9…",
                    "code": "RES-88213",
                    "added": 2,
                    "dropped": 0,
                    "state": "open",
                    "required": 2,
                    "completed": 0,
                    "outstanding": 2,
                    "not_started": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "reservation",
                      "activity"
                    ]
                  },
                  "provider": {
                    "type": "string",
                    "pattern": "^[a-z][a-z0-9_-]*$"
                  },
                  "external_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "starts_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "ends_on": {
                    "nullable": true,
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "facts": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "participants": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "ref": {
                          "type": "string",
                          "maxLength": 64
                        },
                        "full_name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200
                        },
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "phone": {
                          "type": "string",
                          "maxLength": 40
                        },
                        "date_of_birth": {
                          "type": "string",
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        },
                        "is_primary": {
                          "type": "boolean"
                        },
                        "guardian_ref": {
                          "type": "string",
                          "maxLength": 64
                        }
                      },
                      "required": [
                        "full_name"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 60
                  }
                },
                "required": [
                  "external_id",
                  "starts_on"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/packets/code/{code}": {
      "get": {
        "operationId": "get-packets-code-by-code",
        "summary": "Resolve a signing code, from a tablet",
        "description": "The last few feet of a sale made in your own software (00321): the customer holds the signing code from the receipt, the tablet at the counter resolves it and sees what is still owed -- each item's first name and last initial, workflow and state -- and nothing else: no address, no full guest list. Only a paired tablet's credential may ask; a code from another business, a code nobody holds and a malformed code all answer `found: false` the same way. Rate limited per tablet.",
        "tags": [
          "Packets"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "found": true,
                  "packet_id": "p6c9…",
                  "code": "ABCD234567",
                  "state": "open",
                  "required": 2,
                  "completed": 1,
                  "outstanding": 1,
                  "declined": 0,
                  "expires_at": "2026-10-01T00:00:00Z",
                  "items": [
                    {
                      "item_id": "i1a…",
                      "who": "Dana R.",
                      "workflow": "river-float",
                      "required": true,
                      "status": "completed"
                    },
                    {
                      "item_id": "i2b…",
                      "who": "Sam L.",
                      "workflow": "river-float",
                      "required": true,
                      "status": "not_started"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/packets/{id}": {
      "get": {
        "operationId": "get-packets-by-id",
        "summary": "Show a packet",
        "description": "Who owes what, where each one has got to, and a readiness state a door lock can wait on.",
        "tags": [
          "Packets"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "packet_id": "p6c9…",
                  "code": "RES-88213",
                  "status": "open",
                  "ready_at": null,
                  "subject": {
                    "id": "s2e4…",
                    "kind": "reservation",
                    "label": "Reyes party, 4 Oct",
                    "provider": "deckpos",
                    "external_id": "RES-88213",
                    "starts_on": "2026-10-04",
                    "ends_on": "2026-10-04",
                    "facts": {}
                  },
                  "readiness": {
                    "required": 2,
                    "completed": 1,
                    "outstanding": 1,
                    "blocked": 0
                  },
                  "items": [
                    {
                      "item_id": "i1…",
                      "requirement": "waiver",
                      "workflow": "waiver",
                      "required": true,
                      "participant": {
                        "id": "pa1…",
                        "full_name": "Dana Reyes",
                        "email": "dana@example.com",
                        "is_minor": false,
                        "signs_for_them": null
                      },
                      "document_id": "d41c…",
                      "status": "completed"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/packets/items/{id}/start": {
      "post": {
        "operationId": "post-packets-items-by-id-start",
        "summary": "Start one packet item",
        "description": "Renders the item's workflow for the person who owes it. A minor's document is started for their guardian, because the guardian is who signs. A paired tablet may start an item it resolved from a signing code, the way it starts its own workflow.",
        "tags": [
          "Packets"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:subjects",
        "x-or-scope": "signing:workflows",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Your own key, 8–255 characters. The same key with the same body replays the first response without doing the work again; the same key with a different body is refused rather than absorbed.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "document_id": "d41d…",
                  "title": "Kayak rental waiver — Dana Reyes",
                  "expires_at": "2026-10-10T14:22:09Z",
                  "signers": [
                    {
                      "signer_id": "5f0c…",
                      "email": "dana@example.com",
                      "full_name": "Dana Reyes",
                      "role": "signer",
                      "participant": true,
                      "token": "3f9c…"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "operationId": "get-webhooks",
        "summary": "List webhook endpoints",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:webhooks",
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "endpoints": [
                    {
                      "id": "h4a7…",
                      "url": "https://pms.example.com/hooks/signsealer",
                      "label": "Booking system",
                      "events": [
                        "signing.completed",
                        "signing.declined"
                      ],
                      "active": true,
                      "failures": 0,
                      "paused_at": null,
                      "last_delivered_at": "2026-10-03T14:22:09Z",
                      "created_at": "2026-10-03T14:22:09Z",
                      "queued": 0,
                      "dead": 0
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "post-webhooks",
        "summary": "Add a webhook endpoint",
        "description": "Returns the signing secret once and never again. It is sealed at rest; a caller who loses it replaces the endpoint.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:webhooks",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Your own key, 8–255 characters. The same key with the same body replays the first response without doing the work again; the same key with a different body is refused rather than absorbed.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "id": "h4a7…",
                  "secret": "whsec_…48 hex characters, shown once"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "pattern": "^https://"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "maxItems": 30
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 80
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "patch": {
        "operationId": "patch-webhooks-by-id",
        "summary": "Change a webhook endpoint",
        "description": "Turning an endpoint back on forgives its failure count and releases what it held. A paused endpoint keeps receiving events — they wait rather than being lost (00289) — so the answer says how many are waiting to go out, and how many were dropped because the hold was full while it was paused.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:webhooks",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "updated": true
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "active": {
                    "type": "boolean"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "maxItems": 30
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 80
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "delete-webhooks-by-id",
        "summary": "Remove a webhook endpoint",
        "description": "Needs `?reason=`, which goes on the audit trail.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:webhooks",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "deleted": true
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/rotate": {
      "post": {
        "operationId": "post-webhooks-by-id-rotate",
        "summary": "Rotate a webhook endpoint's secret",
        "description": "A new secret, returned once. For 24 hours every delivery is signed with both — the signature header carries two comma-separated `v1=` values, the new secret's first — so a receiver can switch without dropping a delivery. After that the old secret is forgotten.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:webhooks",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "secret": "whsec_…shown once",
                  "previous_valid_for_seconds": 86400
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/deliveries": {
      "get": {
        "operationId": "get-webhooks-by-id-deliveries",
        "summary": "List an endpoint's recent deliveries",
        "description": "The last fifty, newest first: event, status, attempts, the next attempt, the last error and the document. A replay names the delivery it repeats in `replay_of`.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:webhooks",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "deliveries": [
                    {
                      "id": "dl1…",
                      "endpoint_id": "h4a7…",
                      "event": "signing.completed",
                      "status": "delivered",
                      "attempts": 1,
                      "next_attempt_at": "2026-10-03T14:22:09Z",
                      "last_error": null,
                      "response_status": 200,
                      "created_at": "2026-10-03T14:22:09Z",
                      "delivered_at": "2026-10-03T14:22:09Z",
                      "replay_of": null,
                      "document_id": "d41c…",
                      "document_title": "Liability waiver — Dana Reyes"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/deliveries/{id}/replay": {
      "post": {
        "operationId": "post-webhooks-deliveries-by-id-replay",
        "summary": "Send a delivery again",
        "description": "Queues a delivered, failed or dead delivery again as a new delivery with the same event and body, marked as a replay of the original. One still in the queue is refused; so is a second replay while the first is queued. A replay waits like any other if the endpoint is off or paused.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:webhooks",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "delivery_id": "dl2…"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/sign/{token}": {
      "get": {
        "operationId": "get-sign-by-token",
        "summary": "Read a document from a signing link",
        "description": "Everything the signing page needs from one token: the text and its hash, who is being asked, who else is on it, whether it is this signer's turn, and — when it will not take a signature — why, in a sentence a person can read. When the template is an uploaded PDF, `pages` lists its pages and each field may carry a `place` saying where on one it belongs; fetch a page as an image from `/s/{token}/page?n=1`. Returns a body even for a token that matches nothing, so a bad link and an expired one fail identically.",
        "tags": [
          "Signing"
        ],
        "security": [],
        "x-scope": "none",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "reason": null,
                  "document_id": "d41c…",
                  "title": "Liability waiver — Dana Reyes",
                  "body": "I, Dana Reyes, …",
                  "body_sha256_hex": "e362cc76dae17bbe2a9fc551d9b795fa4e0f77fb488eb748378a125b11f36324",
                  "document_status": "sent",
                  "sequential": false,
                  "expires_at": "2026-10-17T00:00:00Z",
                  "tenant_name": "Fern Hollow Stables",
                  "signer_id": "5f0a…",
                  "full_name": "Dana Reyes",
                  "email": "dana@example.com",
                  "signer_role": "signer",
                  "sign_order": 1,
                  "signer_status": "pending",
                  "consented_at": null,
                  "signed_at": null,
                  "template_kind": "waiver",
                  "grants": {},
                  "is_my_turn": true,
                  "waiting_on": null,
                  "consent_text": "By continuing you agree to sign this document electronically and to receive a copy by email. …",
                  "consent_version": "consent/1",
                  "intent_text": "By signing below I confirm that I have read this document, …",
                  "intent_version": "intent/1",
                  "signers": [
                    {
                      "full_name": "Dana Reyes",
                      "role": "signer",
                      "sign_order": 1,
                      "status": "pending",
                      "signed_at": null,
                      "is_me": true
                    }
                  ],
                  "fields": [
                    {
                      "key": "initials",
                      "label": "Initial here",
                      "kind": "initials",
                      "required": true,
                      "about": null,
                      "supplied": false
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "post-sign-by-token",
        "summary": "Sign",
        "description": "Typed or drawn, both equally valid: what matters is intent. `captured` is for a signature taken where there was no network — the server records when it received it and keeps the device's claim beside it, with the clock skew computed rather than accepted.",
        "tags": [
          "Signing"
        ],
        "security": [],
        "x-scope": "none",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "event_id": "e3…"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "method": {
                    "type": "string",
                    "enum": [
                      "typed",
                      "drawn",
                      "clicked",
                      "uploaded"
                    ]
                  },
                  "value": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 400000
                  },
                  "captured": {
                    "type": "object",
                    "properties": {
                      "offline": {
                        "type": "boolean"
                      },
                      "at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "device": {
                        "type": "string",
                        "maxLength": 80
                      }
                    },
                    "additionalProperties": false
                  }
                },
                "required": [
                  "method",
                  "value"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/sign/{token}/consent": {
      "post": {
        "operationId": "post-sign-by-token-consent",
        "summary": "Record consent to sign electronically",
        "description": "Must happen before the signature, and is its own event for that reason: ESIGN requires consent to precede the transaction, and a system that recorded both in the same instant could not show that it did.",
        "tags": [
          "Signing"
        ],
        "security": [],
        "x-scope": "none",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "consented": true
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "captured": {
                    "type": "object",
                    "properties": {
                      "offline": {
                        "type": "boolean"
                      },
                      "at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "device": {
                        "type": "string",
                        "maxLength": 80
                      }
                    },
                    "additionalProperties": false
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/sign/{token}/id-evidence": {
      "get": {
        "operationId": "get-sign-by-token-id-evidence",
        "summary": "What the signer is asked for by way of ID evidence",
        "description": "The business's choice (00228, 00339): `mode` is what the signing page requires of a signer we delivered a link to; `offered` is what the counter offers a signer standing there, who may skip it; `required` says whether the signature will be refused without a photo. With what is captured so far, by hash, and the disclosure each capture is taken under. Null for a token that matches nothing.",
        "tags": [
          "Signing"
        ],
        "security": [],
        "x-scope": "none",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "mode": "off",
                  "offered": "id_selfie",
                  "required": false,
                  "id": {
                    "captured_at": "2026-10-03T14:22:09Z",
                    "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
                  },
                  "selfie": null,
                  "id_disclosure": {
                    "ref": "id_capture/1",
                    "text": "Photograph the front of a government-issued ID. …",
                    "sha256": "e3b0…"
                  },
                  "selfie_disclosure": {
                    "ref": "selfie_capture/1",
                    "text": "If you wish, take a photo of yourself. …",
                    "sha256": "e3b0…"
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "post-sign-by-token-id-evidence",
        "summary": "Keep a photo of the ID, or of the person",
        "description": "The tablet app's camera step (00339). The bytes are shrunk here as the signing page's are, hashed, encrypted under the business's key and kept for thirty days; the capture goes on the trail with its hash and the disclosure shown. Refused when the business did not ask (`offered` is `off`), when a selfie is sent to a business that asked only for the ID, or when the link is closed. The image is never logged.",
        "tags": [
          "Signing"
        ],
        "security": [],
        "x-scope": "none",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "captured": true,
                  "kind": "id",
                  "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "id",
                      "selfie"
                    ]
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "bytes_base64": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1000000
                  },
                  "captured": {
                    "type": "object",
                    "properties": {
                      "offline": {
                        "type": "boolean"
                      },
                      "at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "device": {
                        "type": "string",
                        "maxLength": 80
                      }
                    },
                    "additionalProperties": false
                  }
                },
                "required": [
                  "kind",
                  "content_type",
                  "bytes_base64"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/sign/{token}/location": {
      "get": {
        "operationId": "get-sign-by-token-location",
        "summary": "Whether the signer is asked where they are",
        "description": "The business's choice (00231): `mode` is `off`, `ask` or `require`, and `require` means the signature is refused until a position is on the record. `position` is the answer already given — `{ \"shared\": false }` counts as an answer — and `disclosure` is the wording the capture is taken under, which the client must show before it asks. Null for a token that matches nothing.",
        "tags": [
          "Signing"
        ],
        "security": [],
        "x-scope": "none",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "mode": "require",
                  "position": null,
                  "disclosure": {
                    "ref": "location_capture/2",
                    "text": "You will be asked whether to share where you are. If you allow it, the latitude and longitude your device reports are recorded on this signature …"
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "post-sign-by-token-location",
        "summary": "Record where the signer is, or that they would not say",
        "description": "Send `{ \"shared\": true, \"lat\": …, \"lon\": …, \"accuracy_m\": … }` when the person allowed it, and `{ \"shared\": false }` when they refused or the device could not get a fix — both go on the trail, and both are answers. Ask for the position only after showing the `disclosure` from the GET. Refused when the business does not ask for a location at all, and when the document is already signed.",
        "tags": [
          "Signing"
        ],
        "security": [],
        "x-scope": "none",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "shared": true
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "shared": {
                    "type": "boolean"
                  },
                  "lat": {
                    "type": "number",
                    "minimum": -90,
                    "maximum": 90
                  },
                  "lon": {
                    "type": "number",
                    "minimum": -180,
                    "maximum": 180
                  },
                  "accuracy_m": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100000
                  }
                },
                "required": [
                  "shared"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/sign/{token}/fields/{key}/photo": {
      "post": {
        "operationId": "post-sign-by-token-fields-by-key-photo",
        "summary": "Answer a photo field with a photograph",
        "description": "A template field of kind `photo` (00342) takes a photograph, not text: a JPEG or PNG, shrunk here as the signing page's are, hashed, encrypted under the business's key and kept with the document for as long as the document is kept. The field's value becomes `sha256:<hex>` and the capture goes on the trail as `photo_supplied`. A second photograph replaces the first. `about` names the covered person for a per-child field. Refused for a field that is not a photo, a link that is closed, or an image that is not one.",
        "tags": [
          "Signing"
        ],
        "security": [],
        "x-scope": "none",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "supplied": true,
                  "key": "boat_condition",
                  "sha256": "e362cc76dae17bbe2a9fc551d9b795fa4e0f77fb488eb748378a125b11f36324"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content_type": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "bytes_base64": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1000000
                  },
                  "about": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "captured": {
                    "type": "object",
                    "properties": {
                      "offline": {
                        "type": "boolean"
                      },
                      "at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "device": {
                        "type": "string",
                        "maxLength": 80
                      }
                    },
                    "additionalProperties": false
                  }
                },
                "required": [
                  "content_type",
                  "bytes_base64"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/sign/{token}/code": {
      "post": {
        "operationId": "post-sign-by-token-code",
        "summary": "Send the signer a one-time code",
        "description": "Only when the business asks signers for a code (Settings → Signing) and a link was delivered to this signer. The code goes to the address or number the link went to; the answer says where, masked. One a minute, five an hour. Not an identity check: it proves control of that inbox or phone at the moment of signing.",
        "tags": [
          "Signing"
        ],
        "security": [],
        "x-scope": "none",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "channel": "email",
                  "sent_to": "d•••@example.com",
                  "resend_after": "2026-10-03T14:22:09Z"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/sign/{token}/verify": {
      "post": {
        "operationId": "post-sign-by-token-verify",
        "summary": "Confirm the one-time code",
        "description": "A wrong code is answered with ok false and the tries left rather than an error; after five wrong tries the code is dead and a new one must be sent. A confirmed code is a verified event on the trail and the certificate says email_otp or sms_otp.",
        "tags": [
          "Signing"
        ],
        "security": [],
        "x-scope": "none",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "ok": true
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 4,
                    "maxLength": 12
                  }
                },
                "required": [
                  "code"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/sign/{token}/fields": {
      "post": {
        "operationId": "post-sign-by-token-fields",
        "summary": "Supply a required field",
        "description": "Initials, a checkbox, a date. A signature is refused while anything required is unsupplied, and that refusal is in the database rather than in the form — so a second interface cannot go around it.",
        "tags": [
          "Signing"
        ],
        "security": [],
        "x-scope": "none",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "supplied": true
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "key": {
                    "type": "string",
                    "pattern": "^[a-z][a-z0-9_]{0,58}$"
                  },
                  "value": {
                    "type": "string",
                    "maxLength": 20000
                  },
                  "about": {
                    "nullable": true,
                    "type": "string",
                    "maxLength": 200
                  }
                },
                "required": [
                  "key",
                  "value"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/sign/{token}/decline": {
      "post": {
        "operationId": "post-sign-by-token-decline",
        "summary": "Decline to sign",
        "description": "With a reason. A decline is a person saying no, which is not the same as still waiting — a packet containing one is blocked rather than outstanding, so nobody chases somebody who already answered.",
        "tags": [
          "Signing"
        ],
        "security": [],
        "x-scope": "none",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "declined": true
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/verify/{code}": {
      "get": {
        "operationId": "get-verify-by-code",
        "summary": "Check a certificate by its printed code",
        "description": "Anonymous, because the point is that somebody holding the document can check it. Answers three separate questions: whether the certificate exists, whether a document you hold matches it, and whether it still matches the record behind it. The third is a finding, never a correction. With the code alone you get the issuer, title, dates, fingerprint and how many signed; pass `?sha256=` of the document you hold and, when it matches, the signers (never their email addresses) and the trail.",
        "tags": [
          "Verify"
        ],
        "security": [],
        "x-scope": "none",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "found": true,
                  "verification_code": "K7QP3MTV9XBD2FHR",
                  "issued_at": "2026-10-03T14:22:09Z",
                  "issuer": "Fern Hollow Stables",
                  "title": "Liability waiver — Dana Reyes",
                  "document_sha256": "e362cc76dae17bbe2a9fc551d9b795fa4e0f77fb488eb748378a125b11f36324",
                  "document_matches": true,
                  "still_matches_record": true,
                  "divergence": null,
                  "completed_at": "2026-10-03T14:22:09Z",
                  "redacted_at": null,
                  "signer_count": 1,
                  "signers": [
                    {
                      "full_name": "Dana Reyes",
                      "role": "signer",
                      "sign_order": 1,
                      "status": "signed",
                      "consented_at": "2026-10-03T14:22:09Z",
                      "signed_at": "2026-10-03T14:22:09Z"
                    }
                  ],
                  "events": [
                    {
                      "kind": "signed",
                      "occurred_at": "2026-10-03T14:22:09Z",
                      "signer": "dana@example.com",
                      "against_sha256": "e362cc76dae17bbe2a9fc551d9b795fa4e0f77fb488eb748378a125b11f36324"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "The request could not be read."
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "No key, or a key that is not live. Unknown, revoked and expired all answer the same way."
                  }
                }
              }
            }
          },
          "402": {
            "description": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The included agreements are spent on a plan with no overage, or the account reached its own spending limit. Nothing is wrong with the request."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is live but its scopes do not carry this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "forbidden",
                    "message": "The key is live but its scopes do not carry this."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such thing, in this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such thing, in this account."
                  }
                }
              }
            }
          },
          "405": {
            "description": "That path exists under another method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "That path exists under another method."
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same idempotency key was used for a different request, or the work is still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The same idempotency key was used for a different request, or the work is still running."
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is larger than a signing request ever is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "too_large",
                    "message": "The body is larger than a signing request ever is."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The body did not validate, or the engine refused it. The message is the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "invalid",
                    "message": "The body did not validate, or the engine refused it. The message is the reason."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts. The message says the budget.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many attempts. The message says the budget."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side. Quote the x-request-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "internal",
                    "message": "Something went wrong on our side. Quote the x-request-id."
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
