{
  "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."
      },
      "oauth": {
        "type": "oauth2",
        "description": "For an app other businesses install. The business approves the scopes on SignSealer's consent screen; the access token is sent as `Authorization: Bearer …` like a key, and refreshed with the refresh token. PKCE (S256) is required.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://api.signsealer.com/oauth/authorize",
            "tokenUrl": "https://api.signsealer.com/oauth/token",
            "refreshUrl": "https://api.signsealer.com/oauth/token",
            "scopes": {
              "signing:read": "Read templates, documents, workflows and certificates.",
              "signing:write": "Prepare, send, remind and void documents; hand out signing links.",
              "signing:templates": "Create templates, change drafts, publish, revise and discard them, and change their settings.",
              "signing:workflows": "Start workflows.",
              "signing:webhooks": "Manage webhook endpoints.",
              "signing:subjects": "Reservations and activities, and their packets."
            }
          }
        }
      },
      "partnerAuth": {
        "type": "http",
        "scheme": "basic",
        "description": "For a partner reading its own revenue share: your OAuth `client_id` and `client_secret` as HTTP Basic, the same pair `/oauth/token` takes. Only a confidential client that SignSealer has enrolled in the partner programme can use it; an API key or an access token is refused."
      }
    },
    "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, and the brand it signs under."
    },
    {
      "name": "Templates",
      "description": "The text people sign, and its versions: create, change a draft, publish, revise, discard."
    },
    {
      "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."
    },
    {
      "name": "Standing",
      "description": "Whether somebody's signature on a template is still good."
    },
    {
      "name": "Texting consent",
      "description": "Consent to text a number, taken somewhere SignSealer is not."
    },
    {
      "name": "Devices",
      "description": "Tablets: pairing, heartbeats, and managing a fleet from your own software."
    },
    {
      "name": "Integrations",
      "description": "The booking systems a business connects -- Guesty, Hostaway and others -- and what arrived from each. Read-only here: connecting asks the provider to confirm credentials, which is done in the dashboard."
    },
    {
      "name": "OAuth",
      "description": "Installing a partner's app on a business's account."
    },
    {
      "name": "Partners",
      "description": "A partner's own revenue share: the businesses it brought, what they have paid this month, and every closed month's statement. Read-only, with the partner's client credentials."
    }
  ],
  "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. `frame_origins` lists the sites its signing pages may be framed on (see `POST /v1/documents/{id}/signers/{signer}/embed`). `mode` is `test` for a test key (`mk_test_`), whose account is the business's sandbox: nothing it sends reaches anybody and nothing it does is billed; `live` otherwise. `texting` says whether this business can text people (`available`; `why_not` is `plan` when its plan has no texting and `test` for a sandbox), and `wording` is the opt-in sentence to put beside a \"text me\" box: record exactly those words with `POST /v1/sms-consents` when the box is ticked. Offer no such box when `available` is false -- the consent would be recorded and no text would follow.",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "tenant_id": "3a1f…",
                  "name": "Fern Hollow Stables",
                  "slug": "fern-hollow",
                  "key_label": "Booking system",
                  "scopes": [
                    "signing:read",
                    "signing:write"
                  ],
                  "frame_origins": [
                    "https://*.fernhollow.com",
                    "https://fernhollow.com"
                  ],
                  "mode": "live"
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": [
          "Account"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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 (`GET /v1/account` gives the sentence to show, under `texting.wording`). It has to say that replying STOP opts out, that HELP gets help, that message and data rates may apply, that message frequency varies, that consent is not a condition of purchase, that the number is not shared or sold, and link the terms and the privacy policy; 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": [
          "Texting consent"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:write"
            ]
          }
        ],
        "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": {
          "201": {
            "description": "Made.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "phone": "+15035550142",
                  "purposes": [
                    "signing.link",
                    "signing.reminder",
                    "signing.completed"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": [
          "Texting consent"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "phone",
            "in": "query",
            "required": true,
            "description": "The number, as a person would type it.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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. Without the text: `GET /v1/templates/{id}` has that. Pass `code` for one template's versions.",
        "tags": [
          "Templates"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "code",
            "in": "query",
            "required": false,
            "description": "Only this template's versions.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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,
                      "one_time": false,
                      "show_address": false,
                      "valid_months": 12,
                      "remind_days": 14,
                      "resign_from_version": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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. A question can be asked only when an earlier answer says so (`show_if: {\"field\": \"injured\", \"ticked\": true}`, or `equals` a list of choices, or `answered`); flagged for a person to read (`flag_when`, the same rules); grouped with others under a heading (`block`, with `block_label` and `block_intro` given once, and `block_step: true` for a page of its own); or placed on an uploaded PDF (`place`). The engine checks all of it and refuses by name -- a condition on a question below it, a block with two headings -- and a refused list leaves no template behind.",
        "tags": [
          "Templates"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:templates"
            ]
          }
        ],
        "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": {
          "201": {
            "description": "Made.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "template_id": "8c2e…"
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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
                        },
                        "show_if": {
                          "nullable": true,
                          "type": "object",
                          "additionalProperties": true
                        },
                        "flag_when": {
                          "nullable": true,
                          "type": "object",
                          "additionalProperties": true
                        },
                        "block": {
                          "nullable": true,
                          "type": "string",
                          "maxLength": 59
                        },
                        "block_label": {
                          "nullable": true,
                          "type": "string",
                          "maxLength": 200
                        },
                        "block_intro": {
                          "nullable": true,
                          "type": "string",
                          "maxLength": 2000
                        },
                        "block_step": {
                          "nullable": true,
                          "type": "boolean"
                        },
                        "place": {
                          "nullable": true,
                          "type": "object",
                          "additionalProperties": true
                        }
                      },
                      "required": [
                        "key",
                        "kind"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 50
                  }
                },
                "required": [
                  "code",
                  "name",
                  "kind",
                  "body"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/templates/{id}": {
      "get": {
        "operationId": "get-templates-by-id",
        "summary": "Show one template version",
        "description": "The version's text (`body`, and `body_format`: `text` or `markdown`), its declared `variables`, the consent wording, the questions it asks (`fields`, in the shape `POST /v1/templates` takes them), whether it is published and whether it is the newest version of its code. `settings` belong to the code rather than the version: `one_time`, `show_address`, `photos_in_copy`, `valid_months`, `remind_days` and `resign_from_version`. `versions` lists every version of the code, newest first, so a caller can walk the history -- a published version is never changed or removed, and each document names the exact version it was sent on.",
        "tags": [
          "Templates"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "template_id": "8c2e…",
                  "code": "liability-waiver",
                  "name": "Liability waiver",
                  "kind": "waiver",
                  "version": 2,
                  "is_published": true,
                  "published_at": "2026-10-03T14:22:09Z",
                  "is_latest": true,
                  "created_at": "2026-10-03T14:22:09Z",
                  "body": "# Liability waiver\\n\\nI, {{participant}}, understand that riding carries risk…",
                  "body_format": "markdown",
                  "variables": [
                    "participant"
                  ],
                  "grants": {},
                  "consent_text": "I agree to sign this electronically.",
                  "fields": [
                    {
                      "key": "emergency_contact",
                      "label": "Emergency contact",
                      "kind": "text",
                      "required": true
                    }
                  ],
                  "has_pdf": false,
                  "documents": 118,
                  "change_note": "Names the e-bike and asks for an emergency contact.",
                  "settings": {
                    "one_time": false,
                    "show_address": true,
                    "photos_in_copy": true,
                    "valid_months": 12,
                    "remind_days": 14,
                    "resign_from_version": null
                  },
                  "versions": [
                    {
                      "template_id": "8c2e…",
                      "version": 2,
                      "is_published": true,
                      "published_at": "2026-10-03T14:22:09Z",
                      "name": "Liability waiver",
                      "created_at": "2026-10-03T14:22:09Z",
                      "change_note": "Names the e-bike and asks for an emergency contact.",
                      "has_pdf": false,
                      "documents": 118
                    },
                    {
                      "template_id": "51d7…",
                      "version": 1,
                      "is_published": true,
                      "published_at": "2025-04-02T09:10:00Z",
                      "name": "Liability waiver",
                      "created_at": "2025-04-02T09:02:00Z",
                      "change_note": null,
                      "has_pdf": false,
                      "documents": 342
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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."
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "patch-templates-by-id",
        "summary": "Change a draft",
        "description": "A draft is changed in place until it is published. Any of `name`, `body`, `body_format`, `variables`, `fields` (the whole list, replacing the one it had) and `change_note` (what changed in this version, up to 500 characters, frozen with it when it is published). A published version refuses with 422: what people signed does not change, so make the next version with `POST /v1/templates/{id}/revise`. Answers with the template as `GET /v1/templates/{id}` does.",
        "tags": [
          "Templates"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:templates"
            ]
          }
        ],
        "x-scope": "signing:templates",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "template_id": "8c2e…",
                  "code": "liability-waiver",
                  "name": "Liability waiver",
                  "kind": "waiver",
                  "version": 3,
                  "is_published": false,
                  "published_at": null,
                  "is_latest": true,
                  "created_at": "2026-10-03T14:22:09Z",
                  "body": "# Liability waiver\\n\\nI, {{participant}}, understand that riding carries risk…",
                  "body_format": "markdown",
                  "variables": [
                    "participant"
                  ],
                  "grants": {},
                  "consent_text": "I agree to sign this electronically.",
                  "fields": [
                    {
                      "key": "emergency_contact",
                      "label": "Emergency contact",
                      "kind": "text",
                      "required": true
                    }
                  ],
                  "has_pdf": false,
                  "documents": 0,
                  "change_note": "Asks for an emergency contact.",
                  "settings": {
                    "one_time": false,
                    "show_address": true,
                    "photos_in_copy": true,
                    "valid_months": 12,
                    "remind_days": 14,
                    "resign_from_version": null
                  },
                  "versions": [
                    {
                      "template_id": "8c2e…",
                      "version": 2,
                      "is_published": true,
                      "published_at": "2026-10-03T14:22:09Z",
                      "name": "Liability waiver",
                      "created_at": "2026-10-03T14:22:09Z",
                      "change_note": "Names the e-bike and asks for an emergency contact.",
                      "has_pdf": false,
                      "documents": 118
                    },
                    {
                      "template_id": "51d7…",
                      "version": 1,
                      "is_published": true,
                      "published_at": "2025-04-02T09:10:00Z",
                      "name": "Liability waiver",
                      "created_at": "2025-04-02T09:02:00Z",
                      "change_note": null,
                      "has_pdf": false,
                      "documents": 342
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": 200
                  },
                  "body": {
                    "type": "string",
                    "minLength": 1
                  },
                  "body_format": {
                    "type": "string",
                    "enum": [
                      "text",
                      "markdown"
                    ]
                  },
                  "variables": {
                    "type": "array",
                    "items": {
                      "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
                        },
                        "show_if": {
                          "nullable": true,
                          "type": "object",
                          "additionalProperties": true
                        },
                        "flag_when": {
                          "nullable": true,
                          "type": "object",
                          "additionalProperties": true
                        },
                        "block": {
                          "nullable": true,
                          "type": "string",
                          "maxLength": 59
                        },
                        "block_label": {
                          "nullable": true,
                          "type": "string",
                          "maxLength": 200
                        },
                        "block_intro": {
                          "nullable": true,
                          "type": "string",
                          "maxLength": 2000
                        },
                        "block_step": {
                          "nullable": true,
                          "type": "boolean"
                        },
                        "place": {
                          "nullable": true,
                          "type": "object",
                          "additionalProperties": true
                        }
                      },
                      "required": [
                        "key",
                        "kind"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 50
                  },
                  "change_note": {
                    "nullable": true,
                    "type": "string",
                    "maxLength": 500
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "delete-templates-by-id",
        "summary": "Discard a draft",
        "description": "Only a draft with no documents made from it. A published version is kept for as long as anything signed on it is, so it answers 422; to stop using a template, revise it or stop starting it. Discarding the only version of a code frees the code.",
        "tags": [
          "Templates"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:templates"
            ]
          }
        ],
        "x-scope": "signing:templates",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "template_id": "93aa…",
                  "code": "liability-waiver",
                  "version": 3,
                  "discarded": true
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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/{id}/publish": {
      "post": {
        "operationId": "post-templates-by-id-publish",
        "summary": "Publish a draft",
        "description": "From now on documents made from this template's code use this version, and its text can no longer change. Refused with 422, naming what is wrong, when the body uses a placeholder the template does not declare, when an uploaded PDF has pages nobody has drawn yet, or when it is already published. Answers with the template.",
        "tags": [
          "Templates"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:templates"
            ]
          }
        ],
        "x-scope": "signing:templates",
        "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.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "template_id": "8c2e…",
                  "code": "liability-waiver",
                  "name": "Liability waiver",
                  "kind": "waiver",
                  "version": 2,
                  "is_published": true,
                  "published_at": "2026-10-03T14:22:09Z",
                  "is_latest": true,
                  "created_at": "2026-10-03T14:22:09Z",
                  "body": "# Liability waiver\\n\\nI, {{participant}}, understand that riding carries risk…",
                  "body_format": "markdown",
                  "variables": [
                    "participant"
                  ],
                  "grants": {},
                  "consent_text": "I agree to sign this electronically.",
                  "fields": [
                    {
                      "key": "emergency_contact",
                      "label": "Emergency contact",
                      "kind": "text",
                      "required": true
                    }
                  ],
                  "has_pdf": false,
                  "documents": 118,
                  "change_note": "Names the e-bike and asks for an emergency contact.",
                  "settings": {
                    "one_time": false,
                    "show_address": true,
                    "photos_in_copy": true,
                    "valid_months": 12,
                    "remind_days": 14,
                    "resign_from_version": null
                  },
                  "versions": [
                    {
                      "template_id": "8c2e…",
                      "version": 2,
                      "is_published": true,
                      "published_at": "2026-10-03T14:22:09Z",
                      "name": "Liability waiver",
                      "created_at": "2026-10-03T14:22:09Z",
                      "change_note": "Names the e-bike and asks for an emergency contact.",
                      "has_pdf": false,
                      "documents": 118
                    },
                    {
                      "template_id": "51d7…",
                      "version": 1,
                      "is_published": true,
                      "published_at": "2025-04-02T09:10:00Z",
                      "name": "Liability waiver",
                      "created_at": "2025-04-02T09:02:00Z",
                      "change_note": null,
                      "has_pdf": false,
                      "documents": 342
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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/{id}/revise": {
      "post": {
        "operationId": "post-templates-by-id-revise",
        "summary": "Make the next version of a template",
        "description": "Copies the newest version of this template's code into a new draft, one version higher, with any of `name`, `body`, `variables`, `fields` and `change_note` (what changed, kept in its history) changed; publish it to put it into use. Documents already sent keep the version they were sent on. The id must be the newest version: revising an older one answers 409 with the newest version's id in the hint. A draft is refused too -- change it with `PATCH` instead. Text on an uploaded PDF cannot change; its name and questions can.",
        "tags": [
          "Templates"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:templates"
            ]
          }
        ],
        "x-scope": "signing:templates",
        "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": {
          "201": {
            "description": "Made.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "template_id": "8c2e…",
                  "code": "liability-waiver",
                  "name": "Liability waiver",
                  "kind": "waiver",
                  "version": 3,
                  "is_published": false,
                  "published_at": null,
                  "is_latest": true,
                  "created_at": "2026-10-03T14:22:09Z",
                  "body": "# Liability waiver\\n\\nI, {{participant}}, understand that riding carries risk…",
                  "body_format": "markdown",
                  "variables": [
                    "participant"
                  ],
                  "grants": {},
                  "consent_text": "I agree to sign this electronically.",
                  "fields": [
                    {
                      "key": "emergency_contact",
                      "label": "Emergency contact",
                      "kind": "text",
                      "required": true
                    }
                  ],
                  "has_pdf": false,
                  "documents": 0,
                  "change_note": "Asks for an emergency contact.",
                  "settings": {
                    "one_time": false,
                    "show_address": true,
                    "photos_in_copy": true,
                    "valid_months": 12,
                    "remind_days": 14,
                    "resign_from_version": null
                  },
                  "versions": [
                    {
                      "template_id": "8c2e…",
                      "version": 2,
                      "is_published": true,
                      "published_at": "2026-10-03T14:22:09Z",
                      "name": "Liability waiver",
                      "created_at": "2026-10-03T14:22:09Z",
                      "change_note": "Names the e-bike and asks for an emergency contact.",
                      "has_pdf": false,
                      "documents": 118
                    },
                    {
                      "template_id": "51d7…",
                      "version": 1,
                      "is_published": true,
                      "published_at": "2025-04-02T09:10:00Z",
                      "name": "Liability waiver",
                      "created_at": "2025-04-02T09:02:00Z",
                      "change_note": null,
                      "has_pdf": false,
                      "documents": 342
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": 200
                  },
                  "body": {
                    "type": "string",
                    "minLength": 1
                  },
                  "variables": {
                    "type": "array",
                    "items": {
                      "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
                        },
                        "show_if": {
                          "nullable": true,
                          "type": "object",
                          "additionalProperties": true
                        },
                        "flag_when": {
                          "nullable": true,
                          "type": "object",
                          "additionalProperties": true
                        },
                        "block": {
                          "nullable": true,
                          "type": "string",
                          "maxLength": 59
                        },
                        "block_label": {
                          "nullable": true,
                          "type": "string",
                          "maxLength": 200
                        },
                        "block_intro": {
                          "nullable": true,
                          "type": "string",
                          "maxLength": 2000
                        },
                        "block_step": {
                          "nullable": true,
                          "type": "boolean"
                        },
                        "place": {
                          "nullable": true,
                          "type": "object",
                          "additionalProperties": true
                        }
                      },
                      "required": [
                        "key",
                        "kind"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 50
                  },
                  "change_note": {
                    "type": "string",
                    "maxLength": 500
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/templates/{id}/restore": {
      "post": {
        "operationId": "post-templates-by-id-restore",
        "summary": "Bring an older version back as a new draft",
        "description": "Makes a new draft, one version above the newest, carrying this version's text, questions, consent wording, format and uploaded file; its `change_note` says `Restored from version N`, followed by yours if you give one. Publish it to put it into use. The older version keeps its number and its signatures, and so does every version since. Refused with 422 for the newest version (revise it instead), for a draft, and while the template has an open draft (publish or discard it first).",
        "tags": [
          "Templates"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:templates"
            ]
          }
        ],
        "x-scope": "signing:templates",
        "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": {
          "201": {
            "description": "Made.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "template_id": "c4f0…",
                  "code": "liability-waiver",
                  "name": "Liability waiver",
                  "kind": "waiver",
                  "version": 3,
                  "is_published": false,
                  "published_at": null,
                  "is_latest": true,
                  "created_at": "2026-10-03T14:22:09Z",
                  "body": "I, {{participant}}, understand that riding carries risk…",
                  "body_format": "text",
                  "variables": [
                    "participant"
                  ],
                  "grants": {},
                  "consent_text": "I agree to sign this electronically.",
                  "fields": [],
                  "has_pdf": false,
                  "documents": 0,
                  "change_note": "Restored from version 1: the shorter wording tested better at the counter.",
                  "settings": {
                    "one_time": false,
                    "show_address": true,
                    "photos_in_copy": true,
                    "valid_months": 12,
                    "remind_days": 14,
                    "resign_from_version": null
                  },
                  "versions": [
                    {
                      "template_id": "c4f0…",
                      "version": 3,
                      "is_published": false,
                      "published_at": null,
                      "name": "Liability waiver",
                      "created_at": "2026-10-03T14:22:09Z",
                      "change_note": "Restored from version 1: the shorter wording tested better at the counter.",
                      "has_pdf": false,
                      "documents": 0
                    },
                    {
                      "template_id": "8c2e…",
                      "version": 2,
                      "is_published": true,
                      "published_at": "2026-10-03T14:22:09Z",
                      "name": "Liability waiver",
                      "created_at": "2026-10-03T14:22:09Z",
                      "change_note": "Names the e-bike and asks for an emergency contact.",
                      "has_pdf": false,
                      "documents": 118
                    },
                    {
                      "template_id": "51d7…",
                      "version": 1,
                      "is_published": true,
                      "published_at": "2025-04-02T09:10:00Z",
                      "name": "Liability waiver",
                      "created_at": "2025-04-02T09:02:00Z",
                      "change_note": null,
                      "has_pdf": false,
                      "documents": 342
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": {
                  "change_note": {
                    "type": "string",
                    "maxLength": 450
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/templates/{id}/settings": {
      "patch": {
        "operationId": "patch-templates-by-id-settings",
        "summary": "Change a template's settings",
        "description": "Settings belong to the template's code, not one version, so they carry over to every version after: `one_time` (made to send once, not reused), `show_address` (the business's address printed under it), `photos_in_copy` (the photographs a signer takes as answers are printed, each with its SHA-256, on pages after the certificate in the signed PDF; on unless turned off, and the certificate keeps every hash either way), `valid_months` (how long a signature stays good; null for never), `remind_days` (days before the end each signer is sent a link to sign again; needs `valid_months`) and `resign: true` (everybody who signed an older version must sign the newest published one; false lifts that). Answers with the template.",
        "tags": [
          "Templates"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:templates"
            ]
          }
        ],
        "x-scope": "signing:templates",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "template_id": "8c2e…",
                  "code": "liability-waiver",
                  "name": "Liability waiver",
                  "kind": "waiver",
                  "version": 2,
                  "is_published": true,
                  "published_at": "2026-10-03T14:22:09Z",
                  "is_latest": true,
                  "created_at": "2026-10-03T14:22:09Z",
                  "body": "# Liability waiver\\n\\nI, {{participant}}, understand that riding carries risk…",
                  "body_format": "markdown",
                  "variables": [
                    "participant"
                  ],
                  "grants": {},
                  "consent_text": "I agree to sign this electronically.",
                  "fields": [
                    {
                      "key": "emergency_contact",
                      "label": "Emergency contact",
                      "kind": "text",
                      "required": true
                    }
                  ],
                  "has_pdf": false,
                  "documents": 118,
                  "change_note": "Names the e-bike and asks for an emergency contact.",
                  "settings": {
                    "one_time": false,
                    "show_address": true,
                    "photos_in_copy": true,
                    "valid_months": 12,
                    "remind_days": 14,
                    "resign_from_version": null
                  },
                  "versions": [
                    {
                      "template_id": "8c2e…",
                      "version": 2,
                      "is_published": true,
                      "published_at": "2026-10-03T14:22:09Z",
                      "name": "Liability waiver",
                      "created_at": "2026-10-03T14:22:09Z",
                      "change_note": "Names the e-bike and asks for an emergency contact.",
                      "has_pdf": false,
                      "documents": 118
                    },
                    {
                      "template_id": "51d7…",
                      "version": 1,
                      "is_published": true,
                      "published_at": "2025-04-02T09:10:00Z",
                      "name": "Liability waiver",
                      "created_at": "2025-04-02T09:02:00Z",
                      "change_note": null,
                      "has_pdf": false,
                      "documents": 342
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": {
                  "one_time": {
                    "type": "boolean"
                  },
                  "show_address": {
                    "type": "boolean"
                  },
                  "photos_in_copy": {
                    "type": "boolean"
                  },
                  "valid_months": {
                    "nullable": true,
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 120
                  },
                  "remind_days": {
                    "nullable": true,
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 90
                  },
                  "resign": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/standing": {
      "get": {
        "operationId": "get-standing",
        "summary": "Whether somebody's signature on a template is still good",
        "description": "Pass `template` (the template's code) and `email` or `phone`. Answers with that person's newest completed signature on any version of the template: `standing` is `current` (inside the template's period), `expired` (past it), `resign` (signed on a version the business has since asked everybody to sign again), or `on_file` (signed, and the template has no period); `valid_until` is the day it runs out. `found: false` means they have never signed it, which is the answer \"send them the form\", not an error. The period is set per template in the dashboard, to match what the document's wording says.",
        "tags": [
          "Standing"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "template",
            "in": "query",
            "required": true,
            "description": "The template's code.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "required": false,
            "description": "Whose standing, by email address. This or `phone`.",
            "schema": {
              "type": "string",
              "format": "email"
            }
          },
          {
            "name": "phone",
            "in": "query",
            "required": false,
            "description": "Whose standing, by mobile number. This or `email`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "template": "liability-waiver",
                  "found": true,
                  "document_id": "c30b…",
                  "signed_at": "2026-10-03T14:22:09Z",
                  "template_version": 2,
                  "standing": "current",
                  "valid_until": "2027-10-03T14:22:09Z"
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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/{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": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": "Newest first. Filter with `status` (a state, or `open` for draft and sent), `q` (title, subject or signer) and `external_ref` (your own reference, exactly). Page with `limit` (1–200, 50 by default; more than 200 gets 200) and `offset` (0 by default); `total` is how many match, and `has_more` whether another page follows. Either paging value that is not a whole number is a 400 saying which. A signed document carries `standing` and `valid_until` (00406): whether it is still good, by the period set on its template -- `current`, `expired`, `resign`, `renewed` or `on_file`.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "A state -- `draft`, `sent`, `completed`, `declined`, `expired`, `void` -- or `open` for draft and sent.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Part of a title, a subject's name, or a signer's name, email or number.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "external_ref",
            "in": "query",
            "required": false,
            "description": "Your own reference, as you gave it when you made the document. Exact.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 200; 50 when absent.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "How many to skip; 0 when absent.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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,
                      "standing": null,
                      "valid_until": null,
                      "external_ref": null
                    },
                    {
                      "document_id": "c30b…",
                      "title": "Liability waiver — Sam Lee",
                      "status": "completed",
                      "template_code": "liability-waiver",
                      "template_version": 2,
                      "subject_name": null,
                      "signers": 1,
                      "signed": 1,
                      "waiting_on": null,
                      "sequential": false,
                      "expires_at": null,
                      "sent_at": "2026-10-03T14:22:09Z",
                      "completed_at": "2026-10-03T14:22:09Z",
                      "void_reason": null,
                      "standing": "current",
                      "valid_until": "2027-10-03T14:22:09Z",
                      "external_ref": "booking-20931"
                    }
                  ],
                  "total": 2,
                  "has_more": false,
                  "limit": 50,
                  "offset": 0
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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 newest published version of the template with the values given and stores the result on the document, as a draft. Every declared variable needs a value: a contract that ships with a placeholder still in it is worse than one that failed to send. The exception is the signing date -- `date` and its synonyms (`today`, `todays_date`, `date_signed`, `signed_date`, `signed_on`, `signing_date`, `sign_date`): it is never supplied, a value sent for it is ignored, and the text keeps `{{date}}` until the first signature writes in that day, in the business's timezone. On an uploaded PDF, a question marked `staff` and placed on the page is filled in by whoever sends the document: give it under `values`, keyed as the question is (a date as YYYY-MM-DD). It is checked like an answer, stored as every signer's answer before they sign, shown on their page and not theirs to change, and drawn on the sealed PDF; a required one that is missing is refused with 422, naming it. `external_ref` is your own reference (an order number, a booking id), up to 200 characters: every webhook about the document carries it back as `document.metadata.external_ref`, and `GET /v1/documents?external_ref=` finds it. `metadata` is up to 20 names of your own with text values; it comes back as `document.metadata.custom`. `return_url` is where the signed page offers to take the signer next -- https, on a host the business has proven.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:write"
            ]
          }
        ],
        "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": {
          "201": {
            "description": "Made.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "document_id": "d41c…"
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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"
                  },
                  "external_ref": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string",
                      "maxLength": 500
                    }
                  },
                  "return_url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000
                  }
                },
                "required": [
                  "template_code"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/documents/{id}": {
      "get": {
        "operationId": "get-documents-by-id",
        "summary": "Show one document",
        "description": "The document, its signers, the whole event trail with the text hash each event was made against, and every answer the signers gave. `external_ref`, `metadata` and `return_url` are what you set when you made it. `answers` lists each question in the order the form asks it, with the signer it belongs to. `asked` says whether the question was on that signer's page -- a question hidden by its condition is `asked: false`, has no value, and is never flagged. `flagged` on an answer means the template's flag rule says a person should read it; `flagged` on the document counts them. A question marked sensitive has its value masked, as it is for the business's own staff; a redacted document keeps its questions and gives up every value. Answers are here and not in webhooks on purpose: a webhook goes to whatever address was typed, and some answers are about somebody's health.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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,
                  "standing": "current",
                  "valid_until": "2027-10-03T14:22:09Z",
                  "renewed_by": null,
                  "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
                    }
                  ],
                  "answers": [
                    {
                      "signer_id": "5f0a…",
                      "key": "rode_before",
                      "label": "Have you ridden before?",
                      "kind": "choice",
                      "block": {
                        "key": "experience",
                        "label": "Your riding"
                      },
                      "about": null,
                      "required": true,
                      "sensitive": false,
                      "asked": true,
                      "value": "No",
                      "supplied_at": "2026-10-03T14:22:09Z",
                      "flagged": true
                    },
                    {
                      "signer_id": "5f0a…",
                      "key": "lessons",
                      "label": "How many lessons have you had?",
                      "kind": "number",
                      "block": {
                        "key": "experience",
                        "label": "Your riding"
                      },
                      "about": null,
                      "required": true,
                      "sensitive": false,
                      "asked": false,
                      "value": null,
                      "supplied_at": null,
                      "flagged": false
                    },
                    {
                      "signer_id": "5f0a…",
                      "key": "medical",
                      "label": "Anything we should know medically?",
                      "kind": "paragraph",
                      "block": null,
                      "about": null,
                      "required": false,
                      "sensitive": true,
                      "asked": true,
                      "value": "••••••••••ergy",
                      "supplied_at": "2026-10-03T14:22:09Z",
                      "flagged": false
                    }
                  ],
                  "flagged": 1,
                  "external_ref": "booking-20931",
                  "metadata": {
                    "lesson": "Saturday 10am"
                  },
                  "return_url": null
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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`. Once it is signed, `standing` says whether it is still good (00406): `current`, `expired`, `resign` (the terms changed since), `renewed` (the same person has signed it again since) or `on_file` (no period), with `valid_until`. `link_allowed` says whether this caller may mint a fresh link for it (00409): always for a key, and for a paired tablet only on a document it or its form started, unless the business let that tablet show links for every open document.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "document_id": "d41c…",
                  "title": "Liability waiver — Dana Reyes",
                  "status": "sent",
                  "sent_at": "2026-10-03T14:22:09Z",
                  "completed_at": null,
                  "standing": null,
                  "valid_until": null,
                  "link_allowed": true,
                  "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:write"
            ]
          }
        ],
        "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": {
          "201": {
            "description": "Made.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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}/remind": {
      "post": {
        "operationId": "post-documents-by-id-signers-by-signer-remind",
        "summary": "Send a signer a reminder with a fresh link",
        "description": "Emails (or texts, where they agreed to texts) a new link to one signer who has not signed, and the link they had stops working. Works for an `embedded` signer too, because you asked by name. Refused with 422 inside an hour of their last reminder, for a signer who has signed or declined, a copied recipient, a document that is not out for signature, or somebody there is no way to reach -- and in every one of those cases their link is left as it was.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:write"
            ]
          }
        ],
        "x-scope": "signing:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "signer",
            "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.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "document_id": "d41c…",
                  "signer_id": "5f0a…",
                  "reminded": true,
                  "by": "email"
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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}/embed": {
      "post": {
        "operationId": "post-documents-by-id-signers-by-signer-embed",
        "summary": "A fresh signing link to show in a frame",
        "description": "Mints a new link for a signer who has not signed -- the one they had stops working -- and answers with its `url` and `frame_origins`: the sites the signing page may be framed on, which are the ones the business has proven (a verified signing domain, a proven company sign-in domain, or the site of an app it connected). Put the url in an `<iframe>` on one of them; anywhere else the browser refuses to show it, and an empty list means nowhere yet. Inside a frame the page tells its parent what happened with `postMessage`: `{source: \"signsealer\", event, document_id, signer_id, height}`, where `event` is `ready` on every page, `signed` once they have signed, and `declined`. Recorded on the trail as `link_shown`; ten a minute per document.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:write"
            ]
          }
        ],
        "x-scope": "signing:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "signer",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Made.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "document_id": "d41c…",
                  "signer_id": "7b21…",
                  "url": "https://sign.fernhollow.com/s/b91d…",
                  "frame_origins": [
                    "https://*.fernhollow.com",
                    "https://fernhollow.com"
                  ],
                  "expires_at": "2026-11-02T14:22:09Z"
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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: `token`, and `url`, the page to send them to. The tokens are not stored, only their hashes, so a lost link is replaced rather than looked up -- by `POST /v1/documents/{id}/signers/{signer}/remind`, which sends a new one, or `.../embed`, which hands you one. Each signer is emailed or texted their link unless you send them with `delivery: \"embedded\"`: then nothing is sent to them, and the automatic reminders leave them alone, because you are showing them the page yourself -- in a frame on your site, or in your app. They still need an email address or a mobile number: the certificate records how they could be reached, and the completed copy goes to them.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:write"
            ]
          }
        ],
        "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.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "links": [
                    {
                      "signer_id": "5f0a…",
                      "email": "dana@example.com",
                      "phone": "+16155550142",
                      "delivery": "send",
                      "token": "3f9c…44 base64 characters",
                      "url": "https://sign.fernhollow.com/s/3f9c…"
                    },
                    {
                      "signer_id": "7b21…",
                      "email": "sam@example.com",
                      "phone": null,
                      "delivery": "embedded",
                      "token": "a08e…44 base64 characters",
                      "url": "https://sign.fernhollow.com/s/a08e…"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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"
                        },
                        "delivery": {
                          "type": "string",
                          "enum": [
                            "send",
                            "embedded"
                          ]
                        }
                      },
                      "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": []
          },
          {
            "oauth": [
              "signing:write"
            ]
          }
        ],
        "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.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "voided": true
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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. Sealed once, when the document completed, and kept: every call returns the same bytes, and their SHA-256 is what `GET /v1/verify/:code?sha256=` matches. Returns base64 bytes and the filename to save them under.",
        "tags": [
          "Documents"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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, kept only when it is a day in words within a day of the tablet's clock and not after today. `captured` from anything but a paired tablet is refused with 422. From any other caller a `date` (or a synonym) under `values` is ignored: the text keeps `{{date}}` and the first signature writes in its day. On an uploaded PDF, a box filled in when it is sent (a question marked `staff` and placed on the page) is taken from `fields` or `values` under its key; a required one that is missing is refused, naming it. `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": []
          },
          {
            "oauth": [
              "signing:workflows"
            ]
          }
        ],
        "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": {
          "201": {
            "description": "Made.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:workflows"
            ]
          }
        ],
        "x-scope": "signing:workflows",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:workflows"
            ]
          }
        ],
        "x-scope": "signing:workflows",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "n",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "page": 1,
                  "content_type": "image/png",
                  "bytes_base64": "iVBORw0KGgo…"
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": [
          "Devices"
        ],
        "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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 — and, from 00420, `workflows`: every form it runs, in the order its kiosk lists them — 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": [
          "Devices"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:device",
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "device_id": "de91…",
                  "name": "Front counter iPad",
                  "platform": "android",
                  "workflow_id": "wf3c…",
                  "workflow_name": "River float waiver",
                  "workflows": [
                    {
                      "id": "wf3c…",
                      "name": "River float waiver",
                      "kind": "waiver",
                      "active": true
                    },
                    {
                      "id": "wf7a…",
                      "name": "Campsite rules",
                      "kind": "policy",
                      "active": true
                    }
                  ],
                  "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": [
          "Devices"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:device",
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "report_id": "r7a1…",
                  "device_id": "de91…"
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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 (`workflows`, in the order its kiosk lists them), 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": [
          "Devices"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:devices",
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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,
                      "hand_off": "walk_up",
                      "gate": "own",
                      "workflows": [
                        {
                          "id": "wf3c…",
                          "name": "River float waiver",
                          "kind": "waiver",
                          "active": true
                        },
                        {
                          "id": "wf7a…",
                          "name": "Campsite rules",
                          "kind": "policy",
                          "active": true
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": [
          "Devices"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:devices",
        "responses": {
          "201": {
            "description": "Made.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "id": "de92…",
                  "code": "K7PQ2M9D",
                  "expires_at": "2026-09-16T10:03:15Z",
                  "review": false
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": [
          "Devices"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:devices",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "id": "de92…",
                  "code": "R4WX8NHB",
                  "expires_at": "2026-09-16T10:05:40Z",
                  "review": false
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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, how it is handed a customer, or whose links it may show",
        "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. `workflow_ids` (00420) gives it several forms, in the order its kiosk lists them — a campground's site waiver and its kayak waiver on one tablet — and `workflow_id` becomes the first; `[]` makes it a staff tablet. `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). `gate` (00409) is `own` (the default for a tablet added from 24 September 2026: the Gate screen shows a signing link only for a document this tablet or its form started, and its search matches names, not addresses) or `account` (for every open document, for a tablet at the gate that checks people you emailed; tablets paired earlier were left here). A link a tablet shows is one whoever holds it could open, so open it only on the tablet that needs it.",
        "tags": [
          "Devices"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:devices",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "updated": true
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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"
                  },
                  "workflow_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 12
                  },
                  "hand_off": {
                    "type": "string",
                    "enum": [
                      "walk_up",
                      "same_device",
                      "separate"
                    ]
                  },
                  "gate": {
                    "type": "string",
                    "enum": [
                      "own",
                      "account"
                    ]
                  }
                },
                "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": [
          "Devices"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-scope": "signing:devices",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "reason",
            "in": "query",
            "required": true,
            "description": "Why, for the audit trail.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "revoked": true
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:subjects"
            ]
          }
        ],
        "x-scope": "signing:subjects",
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 4,
              "maxLength": 40
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:subjects"
            ]
          }
        ],
        "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": {
          "201": {
            "description": "Made.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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}/start": {
      "post": {
        "operationId": "post-packets-by-id-start",
        "summary": "Start everything a packet still owes",
        "description": "Starts every item nobody has started yet, each for the person who owes it (a minor's for their guardian). Items already started are left alone, which makes this safe to call on every booking update. An item that cannot be started -- nobody with an email address, an archived workflow -- is listed under `skipped` with the reason, and the others still go. Refused on a voided packet.",
        "tags": [
          "Packets"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:subjects"
            ]
          }
        ],
        "x-scope": "signing:subjects",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "packet_id": "p6c9…",
                  "started": [
                    {
                      "item_id": "i2…",
                      "requirement": "waiver",
                      "participant": "Sam Reyes",
                      "document_id": "d41e…"
                    }
                  ],
                  "skipped": [
                    {
                      "item_id": "i3…",
                      "requirement": "waiver",
                      "participant": "Ana Reyes",
                      "reason": "no address to send Ana Reyes's document to"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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}/void": {
      "post": {
        "operationId": "post-packets-by-id-void",
        "summary": "Void a packet: the booking was cancelled",
        "description": "Voids every document the packet started that nobody has finished, with the reason on each, and marks the packet void. A document somebody already signed is kept -- an executed waiver is evidence of what was agreed, and a cancellation afterwards does not unsign it -- and counted under `kept`.",
        "tags": [
          "Packets"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:write"
            ]
          }
        ],
        "x-scope": "signing:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "packet_id": "p6c9…",
                  "voided": 2,
                  "kept": 1
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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/integrations": {
      "get": {
        "operationId": "get-integrations",
        "summary": "List the booking systems this business can connect, and what it has connected",
        "description": "Every provider with its status (`available`, `beta`, `pending_partner_access`, `planned`, `deprecated`, `disabled`), how it connects and what API version it is built against; and the business's connections with their state, why one is paused when the membership ended (`paused_reason`), when a booking last arrived, the last error and how many events are waiting or gave up. `needs_membership` is true when the business is on Free: connecting a booking system comes with the membership (00425), and a connection paused because the membership ended resumes by itself when it starts again. Credentials and the address a provider posts to are never included. Connecting is done in the dashboard, where the provider is asked to confirm the credentials.",
        "tags": [
          "Integrations"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "providers": [
                    {
                      "key": "guesty",
                      "label": "Guesty",
                      "category": "vacation_rentals",
                      "status": "beta",
                      "connects": "webhook",
                      "auth_type": "oauth_client_credentials",
                      "verification": "svix",
                      "connector_version": "1.0.0",
                      "api_version": "Open API v1; reservations v3; webhooks v2",
                      "deprecates_at": null,
                      "sunsets_at": null
                    }
                  ],
                  "connections": [
                    {
                      "id": "c7a1…",
                      "provider": "guesty",
                      "label": "Guesty (main)",
                      "status": "active",
                      "paused_reason": null,
                      "config": {
                        "send": "on_booking",
                        "void_on_cancel": true,
                        "kind": "reservation"
                      },
                      "created_at": "2026-10-03T14:22:09Z",
                      "last_event_at": "2026-10-03T14:22:09Z",
                      "last_success_at": "2026-10-03T14:22:09Z",
                      "last_error": null,
                      "last_error_at": null,
                      "waiting": 0,
                      "dead": 0
                    }
                  ],
                  "needs_membership": false
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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/integrations/{id}/events": {
      "get": {
        "operationId": "get-integrations-by-id-events",
        "summary": "What arrived from one connection, and what became of it",
        "description": "The latest fifty deliveries, newest first: the provider's booking id, the event, its state (`queued`, `processing`, `done`, `ignored`, `superseded`, `failed`, `dead`), the error when there is one, what the provider's data said about consent to text (`consent_evidence`), the reservation it made and a summary of the packet (how many documents were required, started and skipped, and for each guest with a number whether the link was also texted and why not). What arrived is never returned.",
        "tags": [
          "Integrations"
        ],
        "security": [
          {
            "apiKey": []
          },
          {
            "oauth": [
              "signing:read"
            ]
          }
        ],
        "x-scope": "signing:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "events": [
                    {
                      "id": "e9b2…",
                      "event_type": "reservation.updated.v2",
                      "external_ref": "64f1c2…",
                      "status": "done",
                      "attempts": 1,
                      "received_at": "2026-10-03T14:22:09Z",
                      "processed_at": "2026-10-03T14:22:09Z",
                      "next_attempt_at": "2026-10-03T14:22:09Z",
                      "error": null,
                      "verified_by": "svix",
                      "consent_evidence": "INSUFFICIENT_EVIDENCE",
                      "subject_id": "s2e4…",
                      "result": {
                        "packet_id": "p6c9…",
                        "required": 2,
                        "state": "open",
                        "started": 2,
                        "skipped": [],
                        "sent": true,
                        "texts": [
                          {
                            "guest": "Dana Reyes",
                            "may_text": false,
                            "why": "no consent on file; the link goes by email"
                          }
                        ]
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:webhooks"
            ]
          }
        ],
        "x-scope": "signing:webhooks",
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:webhooks"
            ]
          }
        ],
        "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": {
          "201": {
            "description": "Made.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "id": "h4a7…",
                  "secret": "whsec_…48 hex characters, shown once"
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:webhooks"
            ]
          }
        ],
        "x-scope": "signing:webhooks",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "updated": true
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:webhooks"
            ]
          }
        ],
        "x-scope": "signing:webhooks",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "reason",
            "in": "query",
            "required": true,
            "description": "Why, for the audit trail.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "deleted": true
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:webhooks"
            ]
          }
        ],
        "x-scope": "signing:webhooks",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "secret": "whsec_…shown once",
                  "previous_valid_for_seconds": 86400
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:webhooks"
            ]
          }
        ],
        "x-scope": "signing:webhooks",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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": []
          },
          {
            "oauth": [
              "signing:webhooks"
            ]
          }
        ],
        "x-scope": "signing:webhooks",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests this key may make in the window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in this window.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "When the window turns over, in seconds since the epoch.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "delivery_id": "dl2…"
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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. `body_format` is `text` or `markdown`; a `markdown` body uses headings, bold, italics and lists and should be drawn as such, not as the raw marks. Each field may carry a `block` -- a set of questions asked as one, with a heading and an introduction, and `step: true` when it gets a page of its own -- and a `show_if` condition on an earlier answer. `visible` is the engine's own answer to that condition: draw only the visible questions. A hidden one is not required, and answering the question it depends on differently clears it. `document_after_questions` is true when the business asked for the questions first and the document after them.",
        "tags": [
          "Signing"
        ],
        "security": [],
        "x-scope": "none",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 20,
              "description": "The signing token from the link, URL-encoded."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "reason": null,
                  "document_id": "d41c…",
                  "title": "Liability waiver — Dana Reyes",
                  "body": "# Liability waiver\n\nI, **Dana Reyes**, …",
                  "body_format": "markdown",
                  "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
                    }
                  ],
                  "document_after_questions": false,
                  "fields": [
                    {
                      "key": "rode_before",
                      "label": "Have you ridden before?",
                      "kind": "choice",
                      "required": true,
                      "about": null,
                      "hint": null,
                      "options": [
                        "Yes",
                        "No"
                      ],
                      "sensitive": false,
                      "place": null,
                      "block": {
                        "key": "experience",
                        "label": "Your riding",
                        "intro": "So we can match you to a horse.",
                        "step": true
                      },
                      "show_if": null,
                      "visible": true,
                      "supplied": false
                    },
                    {
                      "key": "lessons",
                      "label": "How many lessons have you had?",
                      "kind": "number",
                      "required": true,
                      "about": null,
                      "hint": null,
                      "options": null,
                      "sensitive": false,
                      "place": null,
                      "block": {
                        "key": "experience",
                        "label": "Your riding",
                        "intro": "So we can match you to a horse.",
                        "step": true
                      },
                      "show_if": {
                        "field": "rode_before",
                        "equals": [
                          "Yes"
                        ]
                      },
                      "visible": false,
                      "supplied": false
                    },
                    {
                      "key": "initials",
                      "label": "Initial here",
                      "kind": "initials",
                      "required": true,
                      "about": null,
                      "hint": null,
                      "options": null,
                      "sensitive": false,
                      "place": null,
                      "block": null,
                      "show_if": null,
                      "visible": true,
                      "supplied": false
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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",
              "minLength": 20,
              "description": "The signing token from the link, URL-encoded."
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Made.",
            "content": {
              "application/json": {
                "example": {
                  "event_id": "e3…"
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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",
              "minLength": 20,
              "description": "The signing token from the link, URL-encoded."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "consented": true
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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",
              "minLength": 20,
              "description": "The signing token from the link, URL-encoded."
            }
          }
        ],
        "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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",
              "minLength": 20,
              "description": "The signing token from the link, URL-encoded."
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Made.",
            "content": {
              "application/json": {
                "example": {
                  "captured": true,
                  "kind": "id",
                  "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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 — for a signer we sent a link to. `offered` is what a counter tablet does with the same choice (00363): `ask` when the business asks anybody, and never more, because a signature at the counter is never refused for the want of a position. `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",
              "minLength": 20,
              "description": "The signing token from the link, URL-encoded."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "mode": "require",
                  "offered": "ask",
                  "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": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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",
              "minLength": 20,
              "description": "The signing token from the link, URL-encoded."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "shared": true
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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
                  },
                  "captured": {
                    "type": "object",
                    "properties": {
                      "offline": {
                        "type": "boolean"
                      },
                      "at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "device": {
                        "type": "string",
                        "maxLength": 80
                      }
                    },
                    "additionalProperties": false
                  }
                },
                "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",
              "minLength": 20,
              "description": "The signing token from the link, URL-encoded."
            }
          },
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z][a-z0-9_]{0,58}$"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Made.",
            "content": {
              "application/json": {
                "example": {
                  "supplied": true,
                  "key": "boat_condition",
                  "sha256": "e362cc76dae17bbe2a9fc551d9b795fa4e0f77fb488eb748378a125b11f36324"
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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",
              "minLength": 20,
              "description": "The signing token from the link, URL-encoded."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "channel": "email",
                  "sent_to": "d•••@example.com",
                  "resend_after": "2026-10-03T14:22:09Z"
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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",
              "minLength": 20,
              "description": "The signing token from the link, URL-encoded."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "ok": true
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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",
              "minLength": 20,
              "description": "The signing token from the link, URL-encoded."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "supplied": true
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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",
              "minLength": 20,
              "description": "The signing token from the link, URL-encoded."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "example": {
                  "declined": true
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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 and how many signed; pass `?sha256=` of the document you hold and, when it matches, the signers (never their email addresses) and the trail. The file itself is checked first: the SHA-256 of the sealed PDF we issued (`copy_matches`), then that of the document's text. `document_matches` is true for either.",
        "tags": [
          "Verify"
        ],
        "security": [],
        "x-scope": "none",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 4,
              "maxLength": 40
            }
          },
          {
            "name": "sha256",
            "in": "query",
            "required": false,
            "description": "The SHA-256 of the sealed PDF you were sent (or of the document's text), in hex. When it matches, the signers and the trail come back too.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "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"
                    }
                  ],
                  "copy_matches": false,
                  "copy_sha256_hex": null
                }
              }
            }
          },
          "400": {
            "description": "`bad_request`: 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": "`unauthorized`: 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": "`quota_exceeded`: The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "quota_exceeded",
                    "message": "The account's credit is used up and could not be reloaded, or it reached the spending limit it set itself. Nothing is wrong with the request, and retrying will not help until credit is added."
                  }
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: 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": "`not_found`: 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": "`method_not_allowed`: 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": "`conflict`: The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which. `template_changed`: A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "conflict": {
                    "value": {
                      "error": {
                        "code": "conflict",
                        "message": "The same idempotency key was used for a different request, or the work is still running; or the thing named has moved on -- a template revised from a version that is no longer the newest. The message says which."
                      }
                    }
                  },
                  "template_changed": {
                    "value": {
                      "error": {
                        "code": "template_changed",
                        "message": "A start named the template version it showed (`expected_template_version`), and a newer one has been published since. Nothing was made; show the person the current wording."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`too_large`: 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": "`invalid`: 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": "`rate_limited`: 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": "`internal`: 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."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/oauth/authorize": {
      "get": {
        "operationId": "oauth-authorize",
        "summary": "Send a business to approve your app",
        "description": "A browser redirect, not an API call: send the person here and they are shown SignSealer's consent screen, signed in to their own account. Query: `response_type=code`, `client_id`, `redirect_uri` (exactly as registered), `scope` (space-separated, within what your app may ask for), `state`, `code_challenge` and `code_challenge_method=S256`. They come back to `redirect_uri` with `code` and your `state`, or with `error`.",
        "tags": [
          "OAuth"
        ],
        "security": [],
        "responses": {
          "302": {
            "description": "To the consent screen."
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "operationId": "oauth-token",
        "summary": "Exchange a code, or refresh a token",
        "description": "RFC 6749, as a form body (JSON is accepted too). The client authenticates with `client_id` and `client_secret` in the body or by HTTP Basic. `grant_type=authorization_code` with `code`, `redirect_uri` and `code_verifier`; or `grant_type=refresh_token` with `refresh_token`. Answers `access_token`, `token_type: bearer`, `expires_in`, `refresh_token` and `scope`, with `Cache-Control: no-store`; or an RFC 6749 error object with status 400 or 401.",
        "tags": [
          "OAuth"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "authorization_code",
                      "refresh_token"
                    ]
                  },
                  "code": {
                    "type": "string"
                  },
                  "redirect_uri": {
                    "type": "string",
                    "format": "uri"
                  },
                  "code_verifier": {
                    "type": "string"
                  },
                  "refresh_token": {
                    "type": "string"
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A token."
          },
          "400": {
            "description": "An RFC 6749 error: `invalid_grant`, `invalid_request`, `unsupported_grant_type`."
          },
          "401": {
            "description": "`invalid_client`."
          }
        }
      }
    },
    "/oauth/register": {
      "post": {
        "operationId": "oauth-register",
        "summary": "A WordPress site registers the client it will connect with",
        "description": "For the WordPress plugin, which does this itself when a site connects. Form body: `kind=wordpress`, `redirect_uri`, `manifest_url` and `token`, all on one https host. SignSealer fetches the manifest and registers the client only when the page says the same token. Ten an hour per address. The secret is in the answer once.",
        "tags": [
          "OAuth"
        ],
        "security": [],
        "responses": {
          "201": {
            "description": "The client, with its secret, once."
          },
          "400": {
            "description": "An RFC 6749-shaped error naming what was wrong."
          },
          "429": {
            "description": "`slow_down`: too many registrations from this address."
          }
        }
      }
    },
    "/v1/partner": {
      "get": {
        "operationId": "get-partner",
        "summary": "Your programme, this month so far, and your signup link",
        "description": "The share you earn, whether your custom branding is on (it is free), how many businesses you have brought, and this month so far: what they paid for, the card fees and texts' cost taken off, and your share. `signup_url` is the link to hand a business: signing up through it makes the business yours. So does installing your app in its first thirty days. `review` is `pending` until a person at SignSealer has approved your account: businesses are recorded as yours meanwhile, and `earning` turns true, with your share counted from `earning_since`, once it is approved.",
        "tags": [
          "Partners"
        ],
        "security": [
          {
            "partnerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Your programme.",
            "content": {
              "application/json": {
                "example": {
                  "partner": "DeckPOS",
                  "client_id": "ss_client_0123456789abcdef0123456789abcdef",
                  "referral_code": "deckpos",
                  "active": true,
                  "custom_branding": true,
                  "review": "approved",
                  "earning": true,
                  "earning_since": "2026-09-02T15:04:05Z",
                  "businesses": 12,
                  "month_to_date": {
                    "month": "2026-09-01",
                    "businesses": 9,
                    "revenue_cents": 41250
                  },
                  "signup_url": "https://app.signsealer.com/signup?partner=deckpos"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorised`: no credentials, the wrong ones, a public client, or a client not enrolled as a partner. One answer for all of them.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`: the partner API only reads."
          }
        }
      }
    },
    "/v1/partner/businesses": {
      "get": {
        "operationId": "get-partner-businesses",
        "summary": "The businesses you brought",
        "description": "Newest first, with how each came to you (`referral_link`, `oauth_install` or `ops`), when, and what its paid use has added this month. `month_to_date_cents` is what it used of what it paid for, after card fees and the carrier's cost of its texts: credit SignSealer gave away earns nothing.",
        "tags": [
          "Partners"
        ],
        "security": [
          {
            "partnerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "description": "At most this many; 50 when left out."
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Skip this many."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of businesses, and whether there are more.",
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "tenant_id": "7d9c1c1e-7a57-4f55-9a86-0f7f3f9c2b11",
                      "name": "Harbour Rentals",
                      "status": "active",
                      "source": "referral_link",
                      "attributed_at": "2026-08-02T15:04:05Z",
                      "month_to_date_cents": 1840
                    }
                  ],
                  "has_more": false
                }
              }
            }
          },
          "400": {
            "description": "`invalid_limit` or `invalid_offset`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorised`: no credentials, the wrong ones, a public client, or a client not enrolled as a partner. One answer for all of them.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`: the partner API only reads."
          }
        }
      }
    },
    "/v1/partner/statements": {
      "get": {
        "operationId": "get-partner-statements",
        "summary": "Every closed month",
        "description": "Newest first. A month is closed on the 1st of the next and never changes after: the businesses and what each added, the share at the rate then in force, the card fees and the carrier's cost of texts taken off before the share, anything carried in, and what is owed. A month that comes to less than nothing is carried into the next rather than invoiced. `paid` turns true when SignSealer records the payment.",
        "tags": [
          "Partners"
        ],
        "security": [
          {
            "partnerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The statements.",
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "month": "2026-08",
                      "businesses": 8,
                      "gross_cents": 39900,
                      "fees_cents": 1250,
                      "sms_cost_cents": 650,
                      "revenue_cents": 38000,
                      "carried_in_cents": 0,
                      "payable_cents": 7210,
                      "carried_out_cents": 0,
                      "paid": true,
                      "paid_at": "2026-09-03T10:00:00Z",
                      "lines": [
                        {
                          "tenant_id": "7d9c1c1e-7a57-4f55-9a86-0f7f3f9c2b11",
                          "name": "Harbour Rentals",
                          "gross_cents": 5400,
                          "fees_cents": 160,
                          "sms_cost_cents": 40,
                          "revenue_cents": 5200
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorised`: no credentials, the wrong ones, a public client, or a client not enrolled as a partner. One answer for all of them.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "`method_not_allowed`: the partner API only reads."
          }
        }
      }
    }
  }
}
