{
  "openapi": "3.0.3",
  "info": {
    "title": "LeonAI SignFlow API",
    "version": "2.0.0",
    "description": "The complete REST surface of LeonAI SignFlow.\n\nEverything the SignFlow web app can do is done through this API — the app is a client of it. An API token can drive the same operations: build templates, send documents for signature, collect signer payments, manage members and roles, configure webhooks and Connect rules, read usage and billing, and (for agencies) run a white-labelled tenant.\n\n## Base URL\n\n`https://signflow-api.leonai.io`\n\nAll paths in this document are absolute. The versioned prefix is `/api/v1`; the same routes are also mounted on `/api` for backward compatibility (an alias, not a redirect — SignFlow never 308s an API path, because that breaks form-encoded replays in Make.com). New integrations should use `/api/v1`.\n\nOrganisations on a custom domain reach the identical API through their own hostname.\n\n## Authentication\n\nThree credentials appear in this document.\n\n**API token** (`ApiToken`) — the credential for server-to-server integrations. Create one on the [API Keys page](https://signflow.leonai.io/api-keys). Pass it *raw* in the Authorization header, with no `Bearer` prefix:\n\n```\nAuthorization: sf_your_token_here\n```\n\nAn API token is bound to exactly one organisation. Requests are automatically scoped to it: list endpoints return that organisation's rows, and `organization_id` in a request body is ignored in favour of the token's own organisation. A token also carries scopes — see below.\n\n**Session JWT** (`SessionAuth`) — what the web app uses, obtained from the OAuth/SSO flows under `/auth`. Passed as `Authorization: Bearer <jwt>` or as the `auth_token` cookie. Endpoints marked with `SessionAuth` only are interactive flows that have no meaning for a machine caller (OAuth redirects, 2FA enrolment).\n\n**Signing token** (`SigningToken`) — a per-signer, per-submission secret minted when a submission is created and delivered in the signing link. It authorises only the signing-ceremony endpoints under `/signing`, for that one signer. It is not an account credential.\n\n## Scopes\n\nAn API token grants a subset of:\n\n| Scope | Grants |\n|---|---|\n| `templates:read` | Read templates, fields, versions, signer roles |\n| `templates:write` | Create/update/delete templates, fields, versions, field detection |\n| `submissions:read` | Read submissions, audit trails, payments, signed PDFs |\n| `submissions:write` | Create, update, void, resend submissions; charge saved cards |\n| `webhooks:read` | Read webhook endpoints and delivery logs |\n| `webhooks:write` | Create/update/delete webhook endpoints, send test deliveries |\n| `organization:read` | Read organisation profile, settings, members, features, usage, billing |\n| `audit:read` | Read and export the organisation audit log |\n| `flows:read` | Read flows, their versions, runs, run logs, incomplete runs, trigger start and push queue, the apps a flow can use, data structures (and generate one from a sample), data stores and their records, and custom variables and their history |\n| `flows:write` | Create, edit, save, restore, trash and delete flows; turn them on and off; set their schedule and trigger start; see, create and rotate a push webhook (its token is a credential); create, change and delete data structures and data stores, and add, replace and delete a data store's records; create, change and delete custom variables |\n| `flows:run` | Run a flow once or via API, replay and stop runs; retry and delete incomplete runs; delete queued push items (retrying with a new mapping also needs `flows:write`) |\n| `integrations:read` | Read SignFlow Connect: providers, connections, the objects, fields, targets, records and events they discover, rules, rule runs and failed jobs |\n| `integrations:write` | Create, change, check and delete Connect connections and rules; turn rules on and off, test them (dry run, suggested mapping), refresh a cached schema and replay failed jobs; see a connection's saved settings and a rule's inbound URL (credentials) |\n| `admin` | Everything above, plus organisation/member/role/billing writes and token management |\n\nEach operation lists the scopes it accepts under `security`. `admin` satisfies every scope check. A request with a valid token that lacks the required scope returns `403` with a `required_scopes` array.\n\nSession JWTs are not scope-limited — a signed-in user's own permissions apply instead (organisation role, and granular permissions when the team-permissions feature is on).\n\n## Errors\n\nErrors are JSON: `{ \"error\": \"human readable message\" }`, often with a machine-readable `code`. Plan-gated features return `402` with `code: \"feature_locked\"` and a `required_tier`.\n\n## Rate limits\n\nLimits are per endpoint and per credential, not a single global bucket — a write-heavy endpoint is tighter than a read. Each operation publishes its own budget in the `x-rate-limit` extension (`{ requests, seconds }`). Exceeding it returns `429`.\n\n## Webhooks\n\nWebhook deliveries are signed Stripe-style in `X-LeonAI-Signature`: `t=<unix>,v1=<hmac_sha256>`, computed over `<timestamp>.<raw body>` with the endpoint's secret. See the `Webhooks` tag and the `WebhookPayload` schema.",
    "contact": {
      "name": "LeonAI Support",
      "url": "https://signflow.leonai.io"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://signflow-api.leonai.io",
      "description": "Production"
    },
    {
      "url": "https://{customDomain}",
      "description": "An organisation's verified custom domain",
      "variables": {
        "customDomain": {
          "default": "sign.example.com",
          "description": "A hostname verified on the organisation's Custom Domain settings"
        }
      }
    }
  ],
  "security": [
    {
      "ApiToken": []
    }
  ],
  "externalDocs": {
    "description": "Guides, quickstarts and webhook reference",
    "url": "https://signflow.leonai.io/api-docs"
  },
  "tags": [
    {
      "name": "Connection",
      "description": "Verify a credential and discover the organisation it is bound to."
    },
    {
      "name": "Templates",
      "description": "Reusable documents: the PDF, its fields, signer roles, versions and detection settings. A template is the thing you send."
    },
    {
      "name": "Template Fields",
      "description": "The field layer of a template — positions, types, validation, assignment to signer roles, and the automatic detection pipeline (text tags, AcroForm, visual placeholders, AI vision)."
    },
    {
      "name": "Submissions",
      "description": "A template sent for signature: signers, their signing links, field values, status and lifecycle (send, remind, void)."
    },
    {
      "name": "Documents",
      "description": "Signed PDFs, audit-trail PDFs and their downloads."
    },
    {
      "name": "Signer Payments",
      "description": "Money collected from a signer during the ceremony — charges, holds and cards saved on file."
    },
    {
      "name": "Webhooks",
      "description": "Outbound event delivery: endpoints, secrets, test sends and the delivery log."
    },
    {
      "name": "API Tokens",
      "description": "Manage the API tokens of an organisation."
    },
    {
      "name": "OAuth (MCP Connector)",
      "description": "OAuth2 authorization-server endpoints that power the SignFlow MCP connector. One registered confidential client (the SignFlow MCP server); browser consent happens on the SignFlow consent page with an organization picker and scope tiers. Grants carry a subset of the API scopes and never `admin`."
    },
    {
      "name": "Files",
      "description": "Upload and retrieve files: source PDFs, signer attachments, logos and signature images."
    },
    {
      "name": "Organizations",
      "description": "The tenant: profile, settings, branding, custom domains, sending domains and payment provider."
    },
    {
      "name": "Members & Invites",
      "description": "Who belongs to an organisation and how they are invited."
    },
    {
      "name": "Roles & Permissions",
      "description": "Custom roles, granular permissions and per-resource access grants."
    },
    {
      "name": "Entitlements",
      "description": "Which features an organisation has, and the platform-owner toggles behind them."
    },
    {
      "name": "Usage & Credits",
      "description": "Metered consumption: credit balance, daily and per-category breakdowns, and usage limits."
    },
    {
      "name": "Billing",
      "description": "Plans, checkout, add-ons, top-ups, invoices, payment method and the billing activity feed."
    },
    {
      "name": "Audit Log",
      "description": "The organisation's tamper-evident event log, its export and its integrity verification."
    },
    {
      "name": "Notifications",
      "description": "In-app notifications for the authenticated user."
    },
    {
      "name": "Profile",
      "description": "The authenticated user's own account settings."
    },
    {
      "name": "Two-Factor Auth",
      "description": "TOTP enrolment, recovery codes, trusted devices and the organisation's 2FA policy."
    },
    {
      "name": "SSO",
      "description": "SAML and OIDC connections, discovery, and the organisation's SSO policy."
    },
    {
      "name": "Connect",
      "description": "SignFlow Connect: connections to external business systems (Priority and others), the dynamic flow rules that fire between them, and the job queue behind them."
    },
    {
      "name": "Flows",
      "description": "Flow engine preview: flows built as a graph of modules, with an autosaved draft, saved versions kept 60 days and a trash kept 30 days. Enabled per organisation by SignFlow; elsewhere every endpoint answers 402 with `feature_locked`. Nothing here runs a flow yet."
    },
    {
      "name": "Agencies",
      "description": "White-label: agencies as managed by the platform owner — creation, staff, clients, hostnames, signing certificates and wholesale invoicing."
    },
    {
      "name": "Agency Console",
      "description": "White-label: the endpoints an agency's own staff use to run their tenant — brand, price book, clients, billing account and hostnames."
    },
    {
      "name": "Signing Ceremony",
      "description": "The signer-facing flow. Authenticated by a per-signer signing token, not by an account credential."
    },
    {
      "name": "Authentication",
      "description": "Interactive sign-in flows: OAuth, SSO callbacks, session refresh and logout."
    },
    {
      "name": "Platform Admin",
      "description": "Platform-owner-only operations across all tenants."
    },
    {
      "name": "System",
      "description": "Health and operational telemetry."
    }
  ],
  "paths": {
    "/api/v1/me": {
      "get": {
        "operationId": "getMe",
        "tags": [
          "Connection"
        ],
        "summary": "Verify the credential and identify its organisation",
        "description": "The cheapest way to confirm a token works and to discover which organisation it is bound to. Integration platforms call this to validate a connection before saving it.\n\n`organization` is null for a session JWT, which is not bound to a single organisation — only API tokens are.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The credential is valid.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "The user the credential acts as."
                        },
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "name": {
                          "type": "string",
                          "nullable": true,
                          "description": "The user's full name."
                        }
                      }
                    },
                    "organization": {
                      "type": "object",
                      "nullable": true,
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "slug": {
                          "type": "string"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "user": {
                    "id": "usr_9fA2",
                    "email": "ops@acme.example",
                    "name": "Dana Levi"
                  },
                  "organization": {
                    "id": "org_31kd",
                    "name": "Acme Corp",
                    "slug": "acme"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "The credential resolved to a user that no longer exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "System"
        ],
        "summary": "Liveness check",
        "description": "Unauthenticated. Also mounted at `/api/health` and `/api/v1/health` so a monitor pointed at any of the three gets the same answer instead of a 401.",
        "security": [],
        "x-rate-limit": {
          "requests": 200,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The worker is serving.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "ok"
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/health/deep": {
      "get": {
        "operationId": "getHealthDeep",
        "tags": [
          "System"
        ],
        "summary": "Dependency check",
        "description": "Probes D1, KV and R2 and reports each. Unauthenticated. Also mounted under `/api` and `/api/v1`.",
        "security": [],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Every dependency answered.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok",
                        "degraded"
                      ]
                    },
                    "checks": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "string"
                      }
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "At least one dependency is failing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/health/errors": {
      "get": {
        "operationId": "getErrorTelemetry",
        "tags": [
          "System"
        ],
        "summary": "Error-rate telemetry",
        "description": "Platform owners only, and deliberately not aliased under `/api`.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Recent error counts by route and status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/templates": {
      "get": {
        "operationId": "listTemplates",
        "tags": [
          "Templates"
        ],
        "summary": "List templates",
        "description": "Returns the organisation's templates, newest-updated first. For an API token the field list of every template is hydrated by default, which is what a data sync wants and what a name/id picker does not — pass `include_fields=false` to skip it.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:read"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationIdQuery"
          },
          {
            "name": "is_active",
            "in": "query",
            "required": false,
            "description": "Filter by active flag. Accepts `true`/`false` or `1`/`0`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum templates to return, capped at 200. Omit for no limit.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "include_fields",
            "in": "query",
            "required": false,
            "description": "Set `false` (or `0`) to omit the per-template field list. API tokens only — session responses never hydrate fields.",
            "schema": {
              "type": "boolean",
              "default": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The matching templates.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TemplateWithFields"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "createTemplate",
        "tags": [
          "Templates"
        ],
        "summary": "Create a template",
        "description": "Upload the PDF first (`POST /api/v1/storage/upload`) and pass its URL as `pdf_url`, or inline it as base64 in `pdf_data`. The PDF's SHA-256 is recorded at this point so later tampering is detectable.\n\nSetting `tax_compliant` locks the Israeli Tax Authority preset: signer OTP, audit trail and approval are forced on and AI detection off, regardless of what else the body says.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TemplateInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Template"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/templates/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getTemplate",
        "tags": [
          "Templates"
        ],
        "summary": "Get a template with its fields",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:read"
        ],
        "responses": {
          "200": {
            "description": "The template.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TemplateWithFields"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "updateTemplate",
        "tags": [
          "Templates"
        ],
        "summary": "Update a template",
        "description": "Partial: only the keys present in the body are written. Replacing `pdf_url` recomputes the stored hash.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TemplateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Template"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteTemplate",
        "tags": [
          "Templates"
        ],
        "summary": "Delete a template",
        "description": "Submissions already created from it are unaffected.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:write"
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/templates/{id}/duplicate": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "duplicateTemplate",
        "tags": [
          "Templates"
        ],
        "summary": "Duplicate a template",
        "description": "Copies the document, its settings and every field. Fields the copy cannot carry over are listed in `stripped`.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:write"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Defaults to the original's name with a copy suffix."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new template.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        }
                      }
                    },
                    "stripped": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Field keys that could not be copied."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/templates/{id}/display-pdf": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getTemplateDisplayPdf",
        "tags": [
          "Templates"
        ],
        "summary": "Download the template's source PDF",
        "description": "Streams the PDF the template was built from — what the builder and the ceremony render.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:read"
        ],
        "responses": {
          "200": {
            "description": "The PDF.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/templates/{id}/fields": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "operationId": "replaceTemplateFields",
        "tags": [
          "Template Fields"
        ],
        "summary": "Replace a template's fields",
        "description": "A full replace, not a merge: the array you send becomes the template's complete field set. Include a field's `id` to preserve its identity (and any stored values keyed to it); omit it to create a new one.\n\n`field_key` is the stable identifier that submissions, prefills and webhooks key by — changing a field's label never changes it.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "fields"
                ],
                "properties": {
                  "fields": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/FieldInput"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored fields.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Field"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/templates/{id}/signer-roles": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listTemplateSignerRoles",
        "tags": [
          "Templates"
        ],
        "summary": "List the signer roles a template uses",
        "description": "The distinct `assigned_signer` values across the template's fields — the roles you supply when creating a submission.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:read"
        ],
        "responses": {
          "200": {
            "description": "The roles.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/templates/{id}/prefill-fields": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listTemplatePrefillFields",
        "tags": [
          "Template Fields"
        ],
        "summary": "List the fields you can prefill",
        "description": "The fields a submission may supply values for, so an integration can build its mapping UI without reading the whole template.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:read"
        ],
        "parameters": [
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "description": "`shared` returns only fields assigned to no signer; `signer` (with `signer_key`) returns one signer's fields. Omit for everything, each annotated with its signer.",
            "schema": {
              "type": "string",
              "enum": [
                "shared",
                "signer"
              ]
            }
          },
          {
            "name": "signer_key",
            "in": "query",
            "required": false,
            "description": "Required when `scope=signer`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The prefillable fields.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "field_key": {
                            "type": "string"
                          },
                          "field_type": {
                            "$ref": "#/components/schemas/FieldType"
                          },
                          "label": {
                            "type": "string",
                            "nullable": true
                          },
                          "assigned_signer": {
                            "type": "string",
                            "nullable": true
                          },
                          "required": {
                            "type": "integer",
                            "enum": [
                              0,
                              1
                            ]
                          },
                          "options": {
                            "type": "string",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/templates/{id}/detect-tags": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "detectTemplateFields",
        "tags": [
          "Template Fields"
        ],
        "summary": "Detect fields in the PDF (dry run)",
        "description": "Runs the detection cascade against the template's PDF and returns what it found **without saving anything**. The cascade stops at the first tier that produces fields: text tags, then AcroForm fields, then visual placeholders, then AI vision, then geometry. The response reports which tiers ran.\n\nUse `POST /apply-tags` to run the same cascade and persist the result.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:read"
        ],
        "parameters": [
          {
            "name": "debug",
            "in": "query",
            "required": false,
            "description": "Include per-tier diagnostics.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "What detection found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "fields": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/FieldInput"
                          }
                        },
                        "tag_count": {
                          "type": "integer"
                        },
                        "has_text_tags": {
                          "type": "boolean"
                        },
                        "has_acroform": {
                          "type": "boolean"
                        },
                        "has_visual_placeholders": {
                          "type": "boolean"
                        },
                        "has_ai_vision": {
                          "type": "boolean"
                        },
                        "has_geometry": {
                          "type": "boolean"
                        },
                        "detection_tiers_used": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "detection_time_ms": {
                          "type": "integer"
                        },
                        "detect_field_types": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ExtendedFieldType"
                          }
                        },
                        "debug": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/templates/{id}/apply-tags": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "applyTemplateDetectedFields",
        "tags": [
          "Template Fields"
        ],
        "summary": "Detect fields and save them",
        "description": "Runs the same cascade as `detect-tags` and writes the result to the template. When the source used text tags, they are redacted from the stored PDF so the markers do not print on the signed document.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:write"
        ],
        "responses": {
          "200": {
            "description": "What was detected and applied.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "fields": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Field"
                          }
                        },
                        "field_count": {
                          "type": "integer"
                        },
                        "redacted": {
                          "type": "boolean",
                          "description": "Whether text-tag markers were removed from the PDF."
                        },
                        "redact_error": {
                          "type": "string",
                          "nullable": true
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/templates/{id}/ai-suggest": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "aiSuggestTemplateFields",
        "tags": [
          "Template Fields"
        ],
        "summary": "Find fields with AI and add them",
        "description": "Reads the pages you send and adds the fields it finds there to the template. A vision model labels the blanks geometry detection found (a blank becomes `id_number` or `signature` with a sensible label) and may add ones it missed. Fields already on the template are never deleted or changed, and a suggestion that overlaps one is dropped, so send only the pages you want read.\n\nSpends one AI credit unless the plan includes unlimited AI. Returns 503 when the AI binding is unavailable. A `tax_compliant` template never sends its pages to the model: it gets geometry detection only.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pages"
                ],
                "properties": {
                  "pages": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "page",
                        "image"
                      ],
                      "properties": {
                        "page": {
                          "type": "integer",
                          "minimum": 1,
                          "description": "The page number, from 1."
                        },
                        "image": {
                          "type": "string",
                          "description": "The page as a PNG, base64 or a `data:image/png;base64,` URL. Rendering happens in the browser because the runtime cannot compile WASM at request time."
                        }
                      }
                    },
                    "description": "The pages to read. Fields are added only on these pages; only the first 10 are read."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The fields added (the template's existing fields are not repeated here), and the suggestions skipped as already filled or not needed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "fields": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/FieldInput"
                          }
                        },
                        "skipped": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          }
                        },
                        "debug": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "description": "AI is not available in this environment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/templates/{id}/ai-verify": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "aiVerifyTemplateFields",
        "tags": [
          "Template Fields"
        ],
        "summary": "Ask the model to check a proposed field set",
        "description": "The judge pass over `ai-suggest`: crops each candidate and confirms or rejects it, so a field the labeller invented over printed text is dropped rather than shipped.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fields": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/FieldInput"
                    }
                  },
                  "page_images": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The surviving fields.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "fields": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/FieldInput"
                          }
                        },
                        "skipped": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "description": "AI is not available in this environment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/templates/{id}/versions": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listTemplateVersions",
        "tags": [
          "Templates"
        ],
        "summary": "List a template's versions",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:read"
        ],
        "responses": {
          "200": {
            "description": "The versions, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "template_id": {
                            "type": "string"
                          },
                          "version_number": {
                            "type": "integer"
                          },
                          "pdf_url": {
                            "type": "string",
                            "nullable": true
                          },
                          "change_summary": {
                            "type": "string",
                            "nullable": true
                          },
                          "created_by": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "createTemplateVersion",
        "tags": [
          "Templates"
        ],
        "summary": "Snapshot the template as a new version",
        "description": "Captures the current PDF and the full field set so you can restore it later.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:write"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "change_summary": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "template_id": {
                          "type": "string"
                        },
                        "version_number": {
                          "type": "integer"
                        },
                        "pdf_url": {
                          "type": "string",
                          "nullable": true
                        },
                        "fields_snapshot": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Field"
                          }
                        },
                        "change_summary": {
                          "type": "string",
                          "nullable": true
                        },
                        "created_by": {
                          "type": "string"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/templates/{id}/versions/{versionId}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "versionId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "operationId": "deleteTemplateVersion",
        "tags": [
          "Templates"
        ],
        "summary": "Delete a version snapshot",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:write"
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/templates/{id}/versions/{versionId}/restore": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "versionId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "restoreTemplateVersion",
        "tags": [
          "Templates"
        ],
        "summary": "Restore a version",
        "description": "Puts the snapshot's PDF and field set back on the template. Submissions already sent keep the document they were sent with.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:write"
        ],
        "responses": {
          "200": {
            "description": "Restored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/templates/{id}/appended-page": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "setTemplateAppendedPage",
        "tags": [
          "Templates"
        ],
        "summary": "Attach a display-only appended PDF",
        "description": "Appended after the signed document and before the audit trail — terms, a price list, anything that must travel with the document but collects no signature. Send either a multipart `file` or a JSON `{ \"url\": ... }`.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "name": {
                    "type": "string"
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Attached.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "append_pdf_url": {
                      "type": "string"
                    },
                    "append_pdf_name": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "removeTemplateAppendedPage",
        "tags": [
          "Templates"
        ],
        "summary": "Remove the appended PDF",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "templates:write"
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/submissions": {
      "get": {
        "operationId": "listSubmissions",
        "tags": [
          "Submissions"
        ],
        "summary": "List submissions",
        "description": "Newest first. An API token sees only its own organisation's submissions.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "submissions:read"
        ],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/SubmissionStatus"
            }
          },
          {
            "name": "template_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/OrganizationIdQuery"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 50
            }
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "The matching submissions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SubmissionWithSigners"
                      }
                    },
                    "total": {
                      "type": "integer",
                      "description": "Total matching rows, ignoring `limit`/`offset`."
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "createSubmission",
        "tags": [
          "Submissions"
        ],
        "summary": "Send a document for signature",
        "description": "The main call. Creates a submission from a template, mints one signing link per signer, and — for signers with an email address — sends the invitation.\n\nA few behaviours worth knowing before you integrate:\n\n- **Signing links come back in the response.** `signers[].signing_url` is the tokenised link for each signer, and the top-level `signing_url` mirrors the first one. Send a signer without an email and SignFlow will not email anyone; you deliver the link yourself.\n- **No signers at all is valid.** You get a token-less link whose first visitor becomes an anonymous signer — the shape a public \"fill this in\" page needs.\n- **Sequential vs parallel** comes from the template's `signing_mode`. In sequential mode only the first signer is invited; the next is invited as each one finishes.\n- **Creation costs a credit** and is refunded if any part of the write fails; a half-built submission is never left behind.\n- **A generated signing password is returned once**, here, in `signing_password`. It is not recoverable afterwards.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "submissions:write"
        ],
        "x-rate-limit": {
          "requests": 50,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSubmissionInput"
              },
              "examples": {
                "single": {
                  "summary": "One signer, prefilled",
                  "value": {
                    "template_id": "tpl_abc123",
                    "name": "NDA — Acme Corp",
                    "signers": [
                      {
                        "name": "Dana Levi",
                        "email": "dana@acme.example",
                        "role": "signer1"
                      }
                    ],
                    "fields_data": {
                      "company_name": "Acme Corp",
                      "effective_date": "2026-09-01"
                    },
                    "metadata": {
                      "crm_deal_id": "D-4417"
                    }
                  }
                },
                "sequential": {
                  "summary": "Two signers in order",
                  "value": {
                    "template_id": "tpl_abc123",
                    "signers": [
                      {
                        "name": "Dana Levi",
                        "email": "dana@acme.example",
                        "role": "signer1",
                        "signing_order": 1
                      },
                      {
                        "name": "Yossi Cohen",
                        "email": "yossi@acme.example",
                        "role": "signer2",
                        "signing_order": 2
                      }
                    ]
                  }
                },
                "linkOnly": {
                  "summary": "No email — take the link and deliver it yourself",
                  "value": {
                    "template_id": "tpl_abc123",
                    "signers": [
                      {
                        "name": "Dana Levi"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created and invitations dispatched.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CreateSubmissionResponse"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "The template does not exist, or is not visible to this credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/submissions/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getSubmission",
        "tags": [
          "Submissions"
        ],
        "summary": "Get a submission with signers and audit log",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "submissions:read"
        ],
        "x-rate-limit": {
          "requests": 120,
          "seconds": 60
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationIdQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The submission.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SubmissionDetail"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "operationId": "updateSubmission",
        "tags": [
          "Submissions"
        ],
        "summary": "Update a submission",
        "description": "Only `pending` and `expired` are settable here — completing a submission is the signing flow's job, and voiding has its own endpoint. Once a submission is terminal (`completed`, `declined`, `voided`, `expired`) any attempt to change its status or overwrite `fields_data` returns 409; a metadata-only edit still succeeds. The check holds even when the submission finishes while the request is on its way: then nothing in the request is written, and the 409 names the status it finished with (or, if it changed and reopened in between, says to try again). Setting `expired` ends the submission as an expiry does: the `submission.expired` webhook is sent and the audit log records who expired it. A `status` outside the two values above, an empty one included, returns 400 (`in_progress` too: it was listed here until 2026-10-14, but production never stored it and such a request failed with 500).",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "submissions:write"
        ],
        "x-rate-limit": {
          "requests": 100,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "pending",
                      "expired"
                    ]
                  },
                  "fields_data": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "expires_at": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Submission"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "deleteSubmission",
        "tags": [
          "Submissions"
        ],
        "summary": "Delete a submission",
        "description": "Removes the submission and its signers, fields and audit rows. To cancel a document that is out for signature while keeping its record, use `POST /void` instead.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "submissions:write"
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/submissions/{id}/void": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "voidSubmission",
        "tags": [
          "Submissions"
        ],
        "summary": "Void a submission",
        "description": "Cancels a document that is out for signature. Signing links stop working immediately and `submission.voided` fires. A finished submission — `completed`, `declined`, `voided` or `expired` — cannot be voided and answers `422` with nothing recorded or sent. This holds even when the submission finishes at the same moment as the void: the void is refused and the finished status stands.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "submissions:write"
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "description": "Recorded on the audit trail."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Voided.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The submission changed while it was being voided. Nothing was voided; retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The submission is finished and cannot be voided. `error` says why: `Cannot void completed submission`, `Cannot void declined submission`, `Cannot void expired submission` or `Submission is already voided`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/submissions/{id}/release": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "releaseSubmission",
        "tags": [
          "Submissions"
        ],
        "summary": "Send a held request",
        "description": "Sends the invitations of a request created with `hold: true` (status `draft`), exactly as creating it without `hold` would have, and gives the signers a fresh expiry window. `pdf_sha256` must be the hash the create response returned: a release names the document that was reviewed. A request that is not held (already released, voided or finished) answers `409` and nothing is sent.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "submissions:write"
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pdf_sha256"
                ],
                "properties": {
                  "pdf_sha256": {
                    "type": "string",
                    "pattern": "^[0-9a-f]{64}$"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Released; invitations sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "status": {
                          "$ref": "#/components/schemas/SubmissionStatus"
                        },
                        "released_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "signers": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "email": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "signing_order": {
                                "type": "integer"
                              },
                              "signing_url": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`pdf_sha256` missing or not 64 lowercase hex characters (`code: pdf_sha256_required`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Not held (`code: not_held`): already released, voided or finished, or another release won. The body's `status` says what the request is now (for example `voided`). Nothing was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "Another document than the one reviewed, or the template now appends pages that were not reviewed (`code: pdf_changed`). Nothing was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "description": "Released, but sending the invitations failed (`code: release_invites_failed`, `status: pending`): resend each signer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/submissions/{id}/resend/{signerId}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "signerId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "resendSignerInvitation",
        "tags": [
          "Submissions"
        ],
        "summary": "Resend a signer's invitation",
        "description": "Emails the reminder again, using the organisation's branding and sending domain, and fires `signer.reminder_sent`. The signer's existing link is reused — it is not rotated. The signer must have an email address and still have to sign; on a document with a signing order, it must be their turn. A document that ended (completed, declined, voided or expired) gets no reminder.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "submissions:write"
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Reminder sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Nothing was sent. `code` says why: `submission_held` (a held request: release it first), `submission_ended` (the document was completed, declined, voided or has expired), `signer_done` (the signer already signed or declined), or `signer_not_current` (the document has a signing order and it is not this signer's turn yet).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "description": "The email could not be sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/submissions/{id}/audit-trail": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "downloadSubmissionAuditTrail",
        "tags": [
          "Documents"
        ],
        "summary": "Download the audit-trail certificate",
        "description": "A standalone PDF certificate: the document's identity and hash, every signer with their timestamps, IP and user agent, and the full event log. Branded with the organisation's (or its agency's) identity.\n\nThis is the certificate as its own file. To get it appended to the signed document instead, set `include_audit_trail` on the template.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "submissions:read"
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The certificate.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/submissions/{id}/generate-signed-pdf": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "generateSignedPdf",
        "tags": [
          "Documents"
        ],
        "summary": "Generate the signed PDF",
        "description": "Flattens every signature and field value onto the document, appends the display-only page and audit trail where configured, and applies the digital signature. Normally unnecessary — completion generates it automatically — but useful to rebuild an artifact.\n\nNote that the signed PDF does **not** exist at the moment `submission.completed` fires; wait for `document.generated` or poll `signed_pdf_url`.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "submissions:write"
        ],
        "responses": {
          "200": {
            "description": "Generated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "signed_pdf_url": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The submission has not been completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/submissions/{id}/signed-pdf": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getSignedPdfUrl",
        "tags": [
          "Documents"
        ],
        "summary": "Get the signed PDF's URL",
        "description": "Returns the location of the signed document without streaming it. Use `/download-signed-pdf` for the bytes.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "submissions:read"
        ],
        "responses": {
          "200": {
            "description": "The URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "signed_pdf_url": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/submissions/{id}/download-signed-pdf": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "downloadSignedPdf",
        "tags": [
          "Documents"
        ],
        "summary": "Download the signed PDF",
        "description": "Streams the completed, digitally-signed document as an attachment and fires `document.downloaded`.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "submissions:read"
        ],
        "responses": {
          "200": {
            "description": "The signed document.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No signed PDF exists yet for this submission.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/submissions/{id}/download": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "downloadSubmissionDocument",
        "tags": [
          "Documents"
        ],
        "summary": "Download the submission's document",
        "description": "The signed PDF once it exists, otherwise the document as it was sent.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "submissions:read"
        ],
        "responses": {
          "200": {
            "description": "The document.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/submissions/{id}/payments": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listSubmissionPayments",
        "tags": [
          "Signer Payments"
        ],
        "summary": "List a submission's payment attempts",
        "description": "Provider-neutral: the same shape whether the organisation is on Pelecard, PayPlus or Sumit. The provider's card token and raw response are deliberately never returned — the token is a credential for charging that card.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "submissions:read"
        ],
        "responses": {
          "200": {
            "description": "The attempts, and whether a re-chargeable card is on file.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "payments": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Payment"
                      }
                    },
                    "has_saved_card": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/submissions/{id}/charge-saved-card": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "chargeSavedCard",
        "tags": [
          "Signer Payments"
        ],
        "summary": "Charge the card a signer saved",
        "description": "Charges the card-on-file left behind by a `J2` save. Requires the signer-payments feature and a configured payment provider.\n\nSend an `idempotency_key` you generate; replaying the same key returns the original outcome with `duplicate: true` rather than charging twice. On success `submission.payment_completed` fires; on rejection `submission.payment_failed` does.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "submissions:write"
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount_agorot"
                ],
                "properties": {
                  "amount_agorot": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Minor units. Zero is rejected by the provider."
                  },
                  "description": {
                    "type": "string"
                  },
                  "idempotency_key": {
                    "type": "string",
                    "description": "Your key for this charge. Reuse it to retry safely."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Charged, or a replay of a charge already made.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "payment_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "paid",
                        "failed",
                        "pending",
                        "unknown"
                      ]
                    },
                    "amount_agorot": {
                      "type": "integer"
                    },
                    "transaction_id": {
                      "type": "string"
                    },
                    "duplicate": {
                      "type": "boolean",
                      "description": "True when this key had already been used."
                    },
                    "error": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No submission, or no card on file.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "operationId": "listIntegrationWebhooks",
        "tags": [
          "Webhooks"
        ],
        "summary": "List the endpoints registered by this token",
        "description": "The API-token surface: the organisation comes from the token, so there is no path parameter. A session JWT cannot call this — use the organisation-scoped routes instead.",
        "security": [
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "webhooks:read"
        ],
        "responses": {
          "200": {
            "description": "The organisation's endpoints, newest first. Secrets are not included.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Called with a session JWT, or the token lacks `webhooks:read`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "attachWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Register an endpoint",
        "description": "Point SignFlow at a URL and choose the events it should receive.\n\nPass `template_id` and the endpoint is written onto that one template instead of the organisation — the same call, a narrower blast radius. Note that in that case the returned `id` is the template's id, which is also what you pass to `DELETE /api/v1/webhooks/{id}` to detach it.\n\nEvery delivery is signed. See the `WebhookPayload` schema and the signature scheme described at the top of this document.\n\nIf the endpoint sits behind a WAF, set `headers` (see `WebhookHeaders`): they are sent on every delivery and retry, stored encrypted, and returned masked.",
        "security": [
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "webhooks:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookInput"
              },
              "example": {
                "name": "Ops notifier",
                "url": "https://hooks.acme.example/signflow",
                "events": [
                  "submission.completed",
                  "submission.declined"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Webhook"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing URL, an unknown event name, a URL rejected as unsafe (not HTTPS, or resolving to a private address), or a custom header that breaks a rule in `WebhookHeaders` — too many, a bad name, a bad value, a duplicate, or a reserved name. The message says which. Also: `headers` together with `template_id`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`template_id` does not belong to this organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "patch": {
        "operationId": "updateIntegrationWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Update a registered endpoint",
        "description": "Partial. Deactivating the endpoint, or moving it to a new URL, also cancels deliveries still queued against the old URL — otherwise a retry could arrive at an address you had just disowned.\n\n`headers` is a merge patch (a string sets, `null` removes, omitted keeps) — see `UpdateWebhookInput`. Each change is audited by header name.",
        "security": [
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "webhooks:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhookInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "updated": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "An unknown event name, an unsafe URL, or a custom header that breaks a rule in `WebhookHeaders` (the merged result is what is checked). The message says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "detachWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Detach an endpoint",
        "description": "Accepts either an organisation endpoint id or a template id — a template-scoped registration is stored on the template, so detaching it means clearing that field. Queued deliveries for the removed URL are cancelled.\n\nNever gated on plan: you can always stop receiving webhooks, whatever your subscription says.",
        "security": [
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "webhooks:write"
        ],
        "responses": {
          "200": {
            "description": "Detached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/webhooks/test": {
      "post": {
        "operationId": "testWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Send a test delivery",
        "description": "Posts a payload from SignFlow's servers and reports what came back — the quickest way to tell a firewall problem from a signature problem. The URL goes through the same safety check as a real registration.\n\nPass `webhook_id` and the test is sent AS that registered endpoint, through the same code path as a live delivery: its custom `headers` are applied and the body is signed with its secret. That is the way to check a WAF rule — a test that skipped the configured header would make a working endpoint look broken. `url` then defaults to the endpoint's URL.\n\nWithout `webhook_id` the delivery is unsigned and carries no custom headers. Either way it is not recorded in delivery history.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-required-scopes": [
          "webhooks:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "One of `url` or `webhook_id` is required.",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Where to post. Required unless `webhook_id` is given; with both, this URL is used."
                  },
                  "webhook_id": {
                    "type": "string",
                    "description": "A registered organisation endpoint. Its stored custom headers and signing secret are applied. With a session JWT you must be an owner or admin of its organisation."
                  },
                  "payload": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Send your own body. Omit it and SignFlow sends a small `test` envelope."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The endpoint answered. `success` reflects the endpoint's status code, not whether SignFlow managed to send.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "integer",
                      "description": "The status code the endpoint returned."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`webhook_id` is not an endpoint of this organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The endpoint could not be reached, took longer than 10 seconds, or (with `webhook_id`) its stored custom headers could not be decrypted — `error` says which.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/webhooks": {
      "parameters": [
        {
          "name": "orgId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listOrgWebhooks",
        "tags": [
          "Webhooks"
        ],
        "summary": "List an organisation's endpoints",
        "description": "The same endpoints as `GET /api/v1/webhooks`, addressed by organisation instead of by token. Any member can read them; secrets come back masked.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoints, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "createOrgWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Create an endpoint for an organisation",
        "description": "Owners and admins only. The signing secret is returned in full in this response and never again — if you lose it, replace the endpoint. Custom `headers` (see `WebhookHeaders`) are accepted here too and come back masked.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 3600
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. `secret` is present here only.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Webhook"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`template_id` does not belong to this organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/webhooks/{webhookId}": {
      "parameters": [
        {
          "name": "orgId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "webhookId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "operationId": "updateOrgWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Update an endpoint",
        "description": "Owners and admins only. Partial despite the verb: omitted keys keep their current value. Deactivating or re-pointing the endpoint cancels deliveries queued against the old URL. `headers` is a merge patch — see `UpdateWebhookInput`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhookInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "operationId": "toggleOrgWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Turn an endpoint on or off",
        "description": "A narrower `PUT` for the common case. Switching it off cancels deliveries already queued against it.\n\nUnlike `PUT`, this is not plan-gated — turning delivery off must always be possible.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "is_active"
                ],
                "properties": {
                  "is_active": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Toggled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "deleteOrgWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete an endpoint",
        "description": "Owners and admins only. Queued deliveries for the URL are cancelled. Never plan-gated.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/webhook-deliveries": {
      "get": {
        "operationId": "listWebhookDeliveries",
        "tags": [
          "Webhooks"
        ],
        "summary": "Delivery history",
        "description": "Every attempt SignFlow made, in reverse chronological order — including the ones that failed and the ones that were cancelled before they left the queue. This is the endpoint to reach for when an integrator says \"we never got the webhook\": the record tells you whether it was sent, refused, or never attempted.\n\nWith no `organization_id`, the result spans every organisation you belong to.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by delivery status. `all` is the same as omitting it.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event",
            "in": "query",
            "required": false,
            "description": "Filter by event type. `all` is the same as omitting it.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/OrganizationIdQuery"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The matching attempts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookDelivery"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/webhook-deliveries/retry-failed": {
      "post": {
        "operationId": "retryFailedDeliveries",
        "tags": [
          "Webhooks"
        ],
        "summary": "Re-send failed deliveries",
        "description": "For after you have fixed the endpoint. Narrow it with `delivery_id` or `webhook_url`; with neither, every failed delivery in the organisation is re-sent, which can be a great many outbound requests at once — hence the rate limit.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 15,
          "seconds": 60
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationIdQuery"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RetryDeliveriesInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How the retry went.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "retried": {
                          "type": "integer",
                          "description": "Attempts made. At most 200 rows are retried per call."
                        },
                        "delivered": {
                          "type": "integer",
                          "description": "How many of those the endpoint accepted."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No `organization_id`, in either the body or the query string.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/tokens": {
      "get": {
        "operationId": "listApiTokens",
        "tags": [
          "API Tokens"
        ],
        "summary": "List API tokens",
        "description": "Owners and admins see every token in the organisation; ordinary members see only their own. `is_own` on each row tells you which is which.\n\nAn API token calling this only ever sees its own organisation's tokens, even if the user behind it belongs to several.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "admin"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrganizationIdQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The tokens, newest first. No secrets — only the last four characters of each.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiToken"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "The token is bound to a different organisation than the one requested.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createApiToken",
        "tags": [
          "API Tokens"
        ],
        "summary": "Create an API token",
        "description": "The secret comes back in this response and is never retrievable again — SignFlow stores only its hash. If it is lost, revoke the token and mint another.\n\nGive each integration its own token with the narrowest scopes that let it do its job: a token that only needs to send documents wants `submissions:write`, not `admin`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "admin"
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiTokenInput"
              },
              "example": {
                "name": "CRM integration",
                "organization_id": "org_31kd",
                "scopes": [
                  "templates:read",
                  "submissions:write"
                ],
                "expiresInDays": 365
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. Copy `token` now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CreatedApiToken"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing `name` or `organization_id`, or a scope that is not one of the nine.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "The organisation's plan does not include API access. Existing tokens keep working; only minting new ones is blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanGateError"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/tokens/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "patch": {
        "operationId": "updateApiToken",
        "tags": [
          "API Tokens"
        ],
        "summary": "Rename a token or change its scopes",
        "description": "Members may edit their own tokens; owners and admins may edit any in the organisation. Scopes are re-validated, so this cannot be used to widen a token beyond the nine known values.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "admin"
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateApiTokenInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "Nothing to update, a non-array `scopes`, or an unknown scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "deleteApiToken",
        "tags": [
          "API Tokens"
        ],
        "summary": "Delete a token",
        "description": "Removes the row outright. Prefer `POST /api/v1/tokens/{id}/revoke` when you want the token to stop working but still want it to resolve in the audit log.\n\nNever plan-gated: taking a credential out of circulation must always be possible.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "admin"
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/tokens/{id}/revoke": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "revokeApiToken",
        "tags": [
          "API Tokens"
        ],
        "summary": "Revoke a token",
        "description": "The token stops authenticating immediately, but the row survives so past audit entries still name it. This is the right call for a leaked credential.\n\nNever plan-gated.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "admin"
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/notifications": {
      "get": {
        "operationId": "listNotifications",
        "tags": [
          "Notifications"
        ],
        "summary": "List your notifications",
        "description": "Scoped to the calling user, newest first.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "The notifications.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Notification"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/notifications/unread-count": {
      "get": {
        "operationId": "getUnreadNotificationCount",
        "tags": [
          "Notifications"
        ],
        "summary": "Count unread notifications",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The count.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "count": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/notifications/mark-all-read": {
      "post": {
        "operationId": "markAllNotificationsRead",
        "tags": [
          "Notifications"
        ],
        "summary": "Mark every notification read",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/notifications/{id}/read": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "markNotificationRead",
        "tags": [
          "Notifications"
        ],
        "summary": "Mark one notification read",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Marked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/notifications/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "operationId": "deleteNotification",
        "tags": [
          "Notifications"
        ],
        "summary": "Delete a notification",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/storage/upload": {
      "post": {
        "operationId": "uploadFile",
        "tags": [
          "Files"
        ],
        "summary": "Upload a PDF or image",
        "description": "The first step in creating a template: upload the PDF here, then pass the returned `url` as `pdf_url` to `POST /api/v1/templates`.\n\nSend `multipart/form-data` with a `file` part. PDF, PNG, JPEG and GIF are accepted, up to 10 MB. The returned key is namespaced per user, so two uploads of the same filename never collide.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "folder": {
                    "type": "string",
                    "default": "uploads",
                    "description": "Groups related uploads. Anything outside letters, digits, `-` and `_` is stripped."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "description": "Relative URL to read the file back. Pass this straight to `pdf_url`."
                    },
                    "key": {
                      "type": "string",
                      "description": "The underlying storage key."
                    }
                  }
                },
                "example": {
                  "url": "/api/storage/file/uploads/usr_9fA2/V1StGX.pdf",
                  "key": "uploads/usr_9fA2/V1StGX.pdf"
                }
              }
            }
          },
          "400": {
            "description": "No file, a type outside the allow-list, or larger than 10 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/storage/file/{key}": {
      "parameters": [
        {
          "name": "key",
          "in": "path",
          "required": true,
          "description": "The storage key, which contains slashes — send it unencoded, as the tail of the path.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "readStoredFile",
        "tags": [
          "Files"
        ],
        "summary": "Read a stored file inline",
        "description": "Serves the object for display — a PDF opens in the viewer rather than downloading.\n\nAccess is decided per object against the caller: an organisation member, or a signer holding a valid signing token for the submission the file belongs to. Holding the key is not itself permission to read it.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          },
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 100,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "The key is malformed or tries to escape its prefix.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteStoredFile",
        "tags": [
          "Files"
        ],
        "summary": "Delete a stored file",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/storage/download/{key}": {
      "parameters": [
        {
          "name": "key",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "downloadStoredFile",
        "tags": [
          "Files"
        ],
        "summary": "Download a stored file as an attachment",
        "description": "The same object and the same permission check as `GET /api/v1/storage/file/{key}`, but sent with a `Content-Disposition: attachment` header so the browser saves it instead of rendering it.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          },
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 50,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The file, as an attachment.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/storage/logo": {
      "post": {
        "operationId": "uploadOrgLogo",
        "tags": [
          "Files"
        ],
        "summary": "Upload an organisation logo",
        "description": "Owners and admins only. PNG, JPEG, GIF or WebP, up to 2 MB. The logo appears on the signing ceremony and on generated documents.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "organization_id"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "organization_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "url": {
                          "type": "string"
                        },
                        "key": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/storage/square-icon": {
      "post": {
        "operationId": "uploadOrgSquareIcon",
        "tags": [
          "Files"
        ],
        "summary": "Upload a square icon",
        "description": "Same rules as the logo. Used where a square crop is needed — the favicon of a custom domain, and the app icon of a white-labelled workspace.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "organization_id"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "organization_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "url": {
                          "type": "string"
                        },
                        "key": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/storage/upload-signature": {
      "post": {
        "operationId": "uploadSignatureImage",
        "tags": [
          "Files"
        ],
        "summary": "Store a drawn signature",
        "description": "Used by the signing ceremony to persist the signature a signer drew, as a PNG data URL. The submission and signer must match an active submission.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data",
                  "submissionId",
                  "signerId"
                ],
                "properties": {
                  "data": {
                    "type": "string",
                    "description": "A `data:image/png;base64,...` URL."
                  },
                  "submissionId": {
                    "type": "string"
                  },
                  "signerId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    },
                    "key": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing data, a body that is not a PNG data URL, or an image over the size cap.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/files/upload": {
      "post": {
        "operationId": "uploadSubmissionFileAsSigner",
        "tags": [
          "Files"
        ],
        "summary": "Attach a file to a submission (signer)",
        "description": "For file fields in the signing ceremony: the signer's own upload, authorised by their signing token rather than by a login.\n\nUp to 10 MB, and only the office/image types SignFlow accepts. Refused once the submission is completed or otherwise no longer active — an attachment must not appear after the document was signed.\n\nThe file is stored under an unguessable path; read it back through `GET /api/v1/files/{fileId}`, never by path.",
        "security": [
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "submission_id",
                  "field_key"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "submission_id": {
                    "type": "string"
                  },
                  "field_key": {
                    "type": "string",
                    "description": "Which file field on the document this belongs to."
                  },
                  "signer_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Attached.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "file": {
                      "$ref": "#/components/schemas/UploadedFile"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not multipart, missing parts, over 10 MB, or a disallowed file type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Invalid signing credentials, or the token belongs to a different signing session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The submission has already been processed, or is no longer active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/files/upload-auth": {
      "post": {
        "operationId": "uploadSubmissionFile",
        "tags": [
          "Files"
        ],
        "summary": "Attach a file to a submission (logged in)",
        "description": "The same upload as `/api/v1/files/upload`, for a member of the organisation rather than a signer. Access is checked against the submission instead of a signing token.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "submission_id",
                  "field_key"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "submission_id": {
                    "type": "string"
                  },
                  "field_key": {
                    "type": "string"
                  },
                  "signer_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Attached.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "file": {
                      "$ref": "#/components/schemas/UploadedFile"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "The submission does not exist, or you cannot see it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/files/{fileId}": {
      "parameters": [
        {
          "name": "fileId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "downloadSubmissionFile",
        "tags": [
          "Files"
        ],
        "summary": "Download an attached file",
        "description": "Streams the file itself. Readable by members of the organisation that owns the submission.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteSubmissionFile",
        "tags": [
          "Files"
        ],
        "summary": "Delete an attached file",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/files/{fileId}/info": {
      "parameters": [
        {
          "name": "fileId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getSubmissionFileInfo",
        "tags": [
          "Files"
        ],
        "summary": "Read an attached file's metadata",
        "description": "Name, type, size and which field it belongs to — without transferring the file.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SubmissionFile"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/files/submission/{submissionId}": {
      "parameters": [
        {
          "name": "submissionId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listSubmissionFiles",
        "tags": [
          "Files"
        ],
        "summary": "List a submission's attachments",
        "description": "Everything uploaded against the submission's file fields, newest first.",
        "security": [
          {
            "ApiToken": []
          },
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The attachments.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SubmissionFile"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/organizations": {
      "get": {
        "operationId": "listOrganizations",
        "tags": [
          "Organizations"
        ],
        "summary": "List organisations",
        "description": "Ordinary users see the organisations they belong to. Platform admins see everything, capped at 500 — except on an agency host, where the view is scoped to that agency's clients.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "organization:read"
        ],
        "responses": {
          "200": {
            "description": "The organisations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Organization"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "createOrganization",
        "tags": [
          "Organizations"
        ],
        "summary": "Create an organisation",
        "description": "The caller becomes its owner. `slug` must be unique across the platform and is limited to lowercase letters, digits and hyphens — it appears in URLs. Browser-session callers must include `turnstileToken` (a Cloudflare Turnstile token minted on the page making the request) once bot protection is enabled; API-token callers are exempt. Not available on an organisation's own custom signing domain.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 3600
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOrganizationInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input, or `turnstile_required` when the bot-check token is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Bot check failed (`turnstile_failed`), or organisation signup is not offered on this domain (`signup_not_available_here`). `error` names which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "That slug is taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The owning agency cannot take on new clients right now — suspended, awaiting legal review, or without authentication configured. `error` names which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "description": "The bot check could not reach Cloudflare (`turnstile_unavailable`). Retry with a fresh token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/with-owner": {
      "post": {
        "operationId": "createOrganizationWithOwner",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Create an organisation and invite its owner",
        "description": "Platform admins only. Creates the organisation and sends an owner invitation to `ownerEmail` in one step, for onboarding a customer who does not have an account yet.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 3600
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "slug",
                  "ownerEmail"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string"
                  },
                  "ownerEmail": {
                    "type": "string",
                    "format": "email"
                  },
                  "logo_url": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created and invited.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "invite_id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "That slug is taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/organizations/my": {
      "get": {
        "operationId": "listMyOrganizations",
        "tags": [
          "Organizations"
        ],
        "summary": "List your memberships",
        "description": "One row per membership, each carrying the organisation it belongs to and your role in it — what an org switcher needs in a single call.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "organization:read"
        ],
        "responses": {
          "200": {
            "description": "Your memberships.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/Member"
                          },
                          {
                            "type": "object",
                            "properties": {
                              "organization": {
                                "$ref": "#/components/schemas/Organization"
                              }
                            }
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/organizations/admin/status": {
      "get": {
        "operationId": "getPlatformAdminStatus",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Am I a platform admin?",
        "description": "Answers for the calling user. Used by the UI to decide whether to show platform-level screens at all.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The answer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "isPlatformAdmin": {
                          "type": "boolean"
                        },
                        "isPlatformOwner": {
                          "type": "boolean"
                        },
                        "role": {
                          "type": "string",
                          "nullable": true,
                          "enum": [
                            "owner",
                            "admin",
                            null
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/organizations/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getOrganization",
        "tags": [
          "Organizations"
        ],
        "summary": "Get an organisation",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "organization:read"
        ],
        "responses": {
          "200": {
            "description": "The organisation, with `settings` parsed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Organization"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "updateOrganization",
        "tags": [
          "Organizations"
        ],
        "summary": "Update an organisation",
        "description": "Owners and admins. Partial: only the keys you send are written. `logo_url` must be either a SignFlow storage path or an `https://` URL — anything else is refused, so a logo cannot be used to point the signing page at a third-party host.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 50,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateOrganizationInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "That slug is taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "deleteOrganization",
        "tags": [
          "Organizations"
        ],
        "summary": "Delete an organisation",
        "description": "Owners only.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/organizations/{id}/settings": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getOrganizationSettings",
        "tags": [
          "Organizations"
        ],
        "summary": "Read organisation settings",
        "description": "Timezone, date format, branding colours, sending defaults and domain state — everything that shapes how signing pages and emails look.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "organization:read"
        ],
        "responses": {
          "200": {
            "description": "The settings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrganizationSettings"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateOrganizationSettings",
        "tags": [
          "Organizations"
        ],
        "summary": "Update organisation settings",
        "description": "Owners and admins. Merged into the existing settings, so send only what changes. Every value is validated — an unknown timezone, a colour that is not `#RRGGBB`, or an expiry outside 1–365 days is a 400 rather than a silently stored bad value.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrganizationSettings"
              },
              "example": {
                "brandColor": "#3B82F6",
                "defaultLanguage": "he",
                "defaultExpirationDays": 14
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The merged settings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrganizationSettings"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A value failed validation; `error` says which one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/organizations/{id}/members": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listMembers",
        "tags": [
          "Members & Invites"
        ],
        "summary": "List members",
        "description": "Any member can read the roster. Each row carries the user's email and profile alongside the membership.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "organization:read"
        ],
        "responses": {
          "200": {
            "description": "The members, in join order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Member"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/organizations/{id}/members/{memberId}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "memberId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "operationId": "updateMemberRole",
        "tags": [
          "Members & Invites"
        ],
        "summary": "Change a member's role",
        "description": "Only an owner may grant the owner role, or change the role of an existing owner. An organisation must keep at least one owner, so demoting the last one is refused.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "role"
                ],
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "owner",
                      "admin",
                      "member"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "That would leave the organisation without an owner.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeMember",
        "tags": [
          "Members & Invites"
        ],
        "summary": "Remove a member",
        "description": "Only an owner may remove another owner, and never the last one.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "That would leave the organisation without an owner.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{id}/invites": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listInvites",
        "tags": [
          "Members & Invites"
        ],
        "summary": "List pending invitations",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "organization:read"
        ],
        "responses": {
          "200": {
            "description": "The invitations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invite"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "createInvite",
        "tags": [
          "Members & Invites"
        ],
        "summary": "Invite someone",
        "description": "Sends an invitation email carrying a single-use token. Only an owner may invite another owner. One pending invitation per email address at a time.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "role"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "owner",
                      "admin",
                      "member"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invited.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "An invitation is already pending for that address, or the person is already a member.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/organizations/{id}/invites/{inviteId}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "inviteId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "operationId": "revokeInvite",
        "tags": [
          "Members & Invites"
        ],
        "summary": "Revoke an invitation",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/organizations/{id}/invites/{inviteId}/resend": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "inviteId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "resendInvite",
        "tags": [
          "Members & Invites"
        ],
        "summary": "Send an invitation again",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 5,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "description": "The email could not be sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/invites/info/{token}": {
      "parameters": [
        {
          "name": "token",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getInviteInfo",
        "tags": [
          "Members & Invites"
        ],
        "summary": "Describe an invitation",
        "description": "Unauthenticated on purpose: the invitation landing page has to render before the recipient has signed in. Returns only what the page shows — which organisation, which role, and whether the link is still good.",
        "security": [],
        "responses": {
          "200": {
            "description": "The invitation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "role": {
                          "type": "string"
                        },
                        "organizationName": {
                          "type": "string",
                          "nullable": true
                        },
                        "expiresAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "isAccepted": {
                          "type": "boolean"
                        },
                        "isExpired": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/organizations/invites/accept/{token}": {
      "parameters": [
        {
          "name": "token",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "acceptInvite",
        "tags": [
          "Members & Invites"
        ],
        "summary": "Accept an invitation",
        "description": "The signed-in user's email must match the address the invitation was sent to. Accepting an invitation you already hold membership for is not an error — you get `already_member` back.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Accepted, or already a member.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "status": {
                          "type": "string",
                          "enum": [
                            "accepted",
                            "already_member"
                          ]
                        },
                        "organizationName": {
                          "type": "string"
                        },
                        "role": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "You are signed in as a different address than the one invited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such invitation, or it was already accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The invitation has expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/invites/my-pending": {
      "get": {
        "operationId": "listMyPendingInvites",
        "tags": [
          "Members & Invites"
        ],
        "summary": "List invitations addressed to you",
        "description": "Matched on the signed-in user's email address.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Your pending invitations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invite"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/organizations/{id}/my-permissions": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getMyPermissions",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "What am I allowed to do here?",
        "description": "Your effective permissions in one organisation: role, the resolved permission list, per-member overrides, and any custom roles you hold. A platform owner gets `[\"*\"]`.\n\nThis is the call a UI makes to decide what to render. It is not the authorisation check itself — the server re-checks on every write regardless.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "organization:read"
        ],
        "responses": {
          "200": {
            "description": "Your effective access.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "role": {
                          "type": "string"
                        },
                        "isOwner": {
                          "type": "boolean"
                        },
                        "permissions": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "overrides": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          }
                        },
                        "customRoles": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "You are not a member of this organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{id}/permissions": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listAvailablePermissions",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "List every permission that exists",
        "description": "The catalogue a role editor renders from — flat, and grouped by area.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "organization:read"
        ],
        "responses": {
          "200": {
            "description": "The catalogue.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "permissions": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          }
                        },
                        "grouped": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/organizations/{id}/permission-enforcement": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getPermissionEnforcement",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "Is granular permission enforcement on?",
        "description": "Granular permissions are opt-in. Until an owner switches this on, custom roles and per-member grants are recorded but not enforced — so an organisation can model its access rules and check them before they start refusing anyone.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "organization:read"
        ],
        "responses": {
          "200": {
            "description": "The current state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "enforce_granular_permissions": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "patch": {
        "operationId": "setPermissionEnforcement",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "Turn granular permission enforcement on or off",
        "description": "Owners only, and only where the team-permissions feature is enabled for the organisation.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled"
                ],
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Set.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "enforce_granular_permissions": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not an owner, or the feature is not enabled for this organisation (`code: FEATURE_DISABLED`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{id}/custom-domain": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "setCustomDomain",
        "tags": [
          "Organizations"
        ],
        "summary": "Set or clear a signing domain",
        "description": "Owners and admins. Registers a custom hostname so signing links are served from your own domain instead of SignFlow's. Send `domain: null` to remove it.\n\nSet `autoConfig` when the domain's DNS provider supports Domain Connect and you want the records written for you; otherwise use `GET .../custom-domain/status` for the records to add by hand, then `POST .../custom-domain/verify`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "domain": {
                    "type": "string",
                    "nullable": true,
                    "example": "sign.acme.example"
                  },
                  "autoConfig": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registered, or removed when `domain` was null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed domain, or a SignFlow system domain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Another organisation already claims that hostname.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "description": "The hostname could not be registered upstream.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{id}/custom-domain/verify": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "verifyCustomDomain",
        "tags": [
          "Organizations"
        ],
        "summary": "Re-check the signing domain's DNS",
        "description": "Call after adding the DNS records. Reports the current hostname and certificate status rather than waiting for it.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The current status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No domain configured, or the platform has no DNS provider configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The hostname is claimed elsewhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "description": "The status check failed upstream.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{id}/custom-domain/status": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getCustomDomainStatus",
        "tags": [
          "Organizations"
        ],
        "summary": "Read the signing domain's status",
        "description": "`configured: false` when no domain is set. Otherwise the hostname state, certificate state and the DNS records still expected.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "organization:read"
        ],
        "responses": {
          "200": {
            "description": "The status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/organizations/{id}/custom-domain/connect-url": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getCustomDomainConnectUrl",
        "tags": [
          "Organizations"
        ],
        "summary": "Get a one-click DNS setup link",
        "description": "Looks up whether the domain's DNS provider supports Domain Connect. When it does, `connectUrl` sends the owner to their provider to approve the records in one step; when it does not, `supported` is false and the records must be added by hand.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "organization:read"
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Whether one-click setup is available.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "supported": {
                          "type": "boolean"
                        },
                        "domain": {
                          "type": "string"
                        },
                        "connectUrl": {
                          "type": "string",
                          "description": "Present only when supported."
                        },
                        "apiUrl": {
                          "type": "string",
                          "description": "The provider's Domain Connect API root."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/organizations/{id}/email-domain": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "setEmailDomain",
        "tags": [
          "Organizations"
        ],
        "summary": "Register a sending domain",
        "description": "So invitations come from your domain rather than SignFlow's. Returns the DKIM, SPF and DMARC records to publish; call `POST .../email-domain/verify` once they are live.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 5,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "domain"
                ],
                "properties": {
                  "domain": {
                    "type": "string",
                    "example": "acme.example"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registered. Publish the returned records.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "domain": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string"
                        },
                        "records": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/DnsRecord"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Another organisation already registered that domain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "description": "The email service is not configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The email provider refused the registration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeEmailDomain",
        "tags": [
          "Organizations"
        ],
        "summary": "Remove the sending domain",
        "description": "Invitations revert to SignFlow's own sending domain.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 5,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/organizations/{id}/email-domain/verify": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "verifyEmailDomain",
        "tags": [
          "Organizations"
        ],
        "summary": "Re-check the sending domain's DNS",
        "description": "`verified` is true only when every record has passed.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The current status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "status": {
                          "type": "string"
                        },
                        "verified": {
                          "type": "boolean"
                        },
                        "records": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/DnsRecord"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "description": "The email service is not configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The status check failed upstream.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{id}/email-domain/connect-url": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getEmailDomainConnectUrl",
        "tags": [
          "Organizations"
        ],
        "summary": "Get a one-click setup link for the sending domain",
        "description": "The email-domain counterpart of the signing-domain connect URL. `supported: false` — with a `reason` where one is known — means the records have to be published by hand.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "organization:read"
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Whether one-click setup is available.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "supported": {
                          "type": "boolean"
                        },
                        "domain": {
                          "type": "string"
                        },
                        "reason": {
                          "type": "string"
                        },
                        "connectUrl": {
                          "type": "string"
                        },
                        "apiUrl": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/organizations/{id}/payment-provider": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Organisation id. An API token may only address the organisation it is bound to.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getPaymentProvider",
        "tags": [
          "Signer Payments"
        ],
        "summary": "Read the signer-payment provider configuration",
        "description": "Credentials are never returned — one non-secret field comes back masked so an operator can confirm which terminal is configured.\n\n`configured: false` with `unusable: true` is a distinct state worth handling: a configuration exists but cannot be decrypted or no longer satisfies the provider's schema. It means \"set up and now broken\", not \"never set up\", which otherwise look identical and send people hunting for a fault at the provider.\n\n`providers` is the catalogue of supported providers and the credential fields each one needs — enough to render the setup form without hard-coding it.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentProviderConfig"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "put": {
        "operationId": "setPaymentProvider",
        "tags": [
          "Signer Payments"
        ],
        "summary": "Configure the signer-payment provider",
        "description": "Owners and admins, and only where the signer-payments feature is enabled. Credentials are encrypted at rest and never read back.\n\n`default_action_type` picks what a signer's payment does by default — charge, hold, or save a card for later. Note that capturing a hold when the document completes is opt-in: `auto_capture_on_complete` is false unless you set it, so a hold stays a hold until somebody decides to take the money.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetPaymentProviderInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Configured.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "configured": {
                      "type": "boolean"
                    },
                    "provider": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown provider, missing credentials, or credentials the provider rejected. `code` distinguishes them.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not an owner or admin, or signer payments are not enabled for this organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "deletePaymentProvider",
        "tags": [
          "Signer Payments"
        ],
        "summary": "Remove the payment configuration",
        "description": "Signers stop being asked to pay. Payments already taken are unaffected.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "configured": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/usage": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "getUsage",
        "tags": [
          "Usage & Credits"
        ],
        "summary": "Usage this calendar month",
        "description": "Submissions and signatures used against the organisation's limits, for the current UTC month. A `null` limit means unlimited, and `*_remaining` is null to match — do not read it as zero.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The current period's usage.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UsageSummary"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/usage/record": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "getUsageRecord",
        "tags": [
          "Usage & Credits"
        ],
        "summary": "Read the stored usage record",
        "description": "The persisted counter row, where one exists. `data` is null for an organisation that has no record yet — which is not an error, just a new organisation.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The record, or null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "nullable": true,
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/usage/credits/daily": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "getDailyCreditUsage",
        "tags": [
          "Usage & Credits"
        ],
        "summary": "Credits spent per day",
        "description": "For a usage chart. Every day in the window is present, including the ones with no spend, so the series needs no gap-filling at the client. Counts metered spend only — grants and refunds are excluded.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Window length, ending today.",
            "schema": {
              "type": "integer",
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The daily series.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "days": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "date": {
                                "type": "string",
                                "format": "date"
                              },
                              "credits": {
                                "type": "number"
                              }
                            }
                          }
                        },
                        "averagePerDay": {
                          "type": "number"
                        },
                        "totalSpent": {
                          "type": "number"
                        },
                        "rangeDays": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/usage/credits/breakdown": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "getCreditBreakdown",
        "tags": [
          "Usage & Credits"
        ],
        "summary": "What the credits went on",
        "description": "Spend grouped by reason, plus the most recent individual entries — the answer to \"why did we use so many credits this month?\"",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The breakdown.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "byReason": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "reason": {
                                "type": "string"
                              },
                              "credits": {
                                "type": "number"
                              },
                              "count": {
                                "type": "integer"
                              }
                            }
                          }
                        },
                        "recent": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          }
                        },
                        "totalSpent": {
                          "type": "number"
                        },
                        "rangeDays": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/usage/limits": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "put": {
        "operationId": "updateUsageLimits",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Set an organisation's usage limits",
        "description": "Platform admins only. Send null for a limit to make it unlimited.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "monthly_submission_limit": {
                    "type": "integer",
                    "nullable": true
                  },
                  "monthly_signature_limit": {
                    "type": "integer",
                    "nullable": true
                  },
                  "max_submissions": {
                    "type": "integer",
                    "nullable": true
                  },
                  "max_api_calls": {
                    "type": "integer",
                    "nullable": true
                  },
                  "max_storage_bytes": {
                    "type": "integer",
                    "nullable": true
                  },
                  "max_templates": {
                    "type": "integer",
                    "nullable": true
                  },
                  "max_team_members": {
                    "type": "integer",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/features": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "getOrganizationFeatures",
        "tags": [
          "Entitlements"
        ],
        "summary": "Which features this organisation has",
        "description": "Two views of the same list. The top-level flags are what is **effective** — what the organisation can actually use right now, whether that comes from its plan, a purchased add-on, or a platform-owner grant. `manual` is only the owner grants.\n\nThey differ in a way that matters when debugging: an organisation can have `feature_integrations: 1` effective while `manual.feature_integrations` is 0, because it bought the add-on rather than being given it. Turning the manual grant off would not take the feature away.\n\n`feature_flow_engine` (the flow builder) is read-only here: it is top-level only, with no `manual` copy, because PUT does not set it.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The feature flags.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrganizationFeatures"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "put": {
        "operationId": "setOrganizationFeatures",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Grant or revoke features",
        "description": "Platform admins only. Writes the **manual** grants — the owner-side switches. A feature the organisation gets from its plan or a paid add-on is unaffected by turning the manual grant off. `feature_flow_engine` is not one of the flags written here: a body that carries it leaves it as it is.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetOrganizationFeaturesInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/audit-log": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "listAuditEvents",
        "tags": [
          "Audit Log"
        ],
        "summary": "Read the audit log",
        "description": "Every recorded action in the organisation, newest first, with the filters a compliance review actually uses.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "audit:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "name": "resource_type",
            "in": "query",
            "required": false,
            "description": "e.g. `submission`, `template`, `api_key`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_type",
            "in": "query",
            "required": false,
            "description": "e.g. `created`, `signed`, `revoked`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "submission_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start_date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "end_date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The matching events.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AuditEvent"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "offset": {
                          "type": "integer"
                        },
                        "hasMore": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/audit-log/export": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "exportAuditLog",
        "tags": [
          "Audit Log"
        ],
        "summary": "Export the audit log",
        "description": "The same records as a file, for handing to an auditor.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "audit:read"
        ],
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "csv",
                "json"
              ],
              "default": "csv"
            }
          },
          {
            "name": "resource_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start_date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "end_date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The export, as an attachment.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              },
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AuditEvent"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/audit-log/verify": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "verifyAuditChain",
        "tags": [
          "Audit Log"
        ],
        "summary": "Verify a submission's audit chain",
        "description": "Audit entries are hash-chained, so a record cannot be altered or removed after the fact without breaking the chain. This walks one submission's chain and reports whether it still holds — the evidence behind a signed document's trail.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "audit:read"
        ],
        "parameters": [
          {
            "name": "submission_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The verification result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "valid": {
                      "type": "boolean"
                    },
                    "entries": {
                      "type": "integer",
                      "description": "How many links were checked."
                    },
                    "brokenAt": {
                      "type": "string",
                      "nullable": true,
                      "description": "The first entry whose hash did not match, when the chain is broken."
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/organizations/submissions/{submissionId}": {
      "parameters": [
        {
          "name": "submissionId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getSubmissionAuditLog",
        "tags": [
          "Audit Log"
        ],
        "summary": "Read one submission's audit trail",
        "description": "Every recorded event for a single submission, in order. Note the path: this route lives under `/organizations` because it is served by the audit-log module, not under `/submissions`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "audit:read"
        ],
        "responses": {
          "200": {
            "description": "The events.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AuditEvent"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/profile": {
      "get": {
        "operationId": "getProfile",
        "tags": [
          "Profile"
        ],
        "summary": "Get your profile",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Your profile.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Profile"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "updateProfile",
        "tags": [
          "Profile"
        ],
        "summary": "Update your profile",
        "description": "Partial; sending no recognised field is a 400 rather than a silent no-op.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "full_name": {
                    "type": "string"
                  },
                  "company_name": {
                    "type": "string"
                  },
                  "timezone": {
                    "type": "string"
                  },
                  "language": {
                    "type": "string",
                    "enum": [
                      "en",
                      "he"
                    ]
                  },
                  "theme": {
                    "type": "string",
                    "enum": [
                      "light",
                      "dark",
                      "system"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated profile.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Profile"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/profile/theme": {
      "patch": {
        "operationId": "setProfileTheme",
        "tags": [
          "Profile"
        ],
        "summary": "Set your theme",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "theme"
                ],
                "properties": {
                  "theme": {
                    "type": "string",
                    "enum": [
                      "light",
                      "dark",
                      "system"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Set.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "theme": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/profile/language": {
      "patch": {
        "operationId": "setProfileLanguage",
        "tags": [
          "Profile"
        ],
        "summary": "Set your language",
        "description": "Changes the interface language, and the language of emails addressed to you.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "language"
                ],
                "properties": {
                  "language": {
                    "type": "string",
                    "enum": [
                      "en",
                      "he"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Set.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "language": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/profile/notifications": {
      "get": {
        "operationId": "getNotificationPreferences",
        "tags": [
          "Profile"
        ],
        "summary": "Read your email preferences",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Your preferences.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NotificationPreferences"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "put": {
        "operationId": "setNotificationPreferences",
        "tags": [
          "Profile"
        ],
        "summary": "Set your email preferences",
        "description": "Controls which document events email you. It does not affect webhooks, which are configured per organisation.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NotificationPreferences"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored preferences.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NotificationPreferences"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/roles": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "listRoles",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "List custom roles",
        "description": "Requires the team-permissions feature. Each role carries how many members hold it.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The roles.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Role"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not a member, or the team-permissions feature is not enabled here.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "post": {
        "operationId": "createRole",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "Create a custom role",
        "description": "Admins and owners. Every permission id in `permissions` is checked against the catalogue from `GET /api/v1/organizations/{id}/permissions`; an unknown id fails the whole call and comes back listed in `details.invalidIds`, so a typo cannot create a role that quietly grants less than intended.\n\n`base_role` starts the role from a built-in one's permissions. `priority` decides which role wins when a member holds several.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RoleInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or over-long name, or unknown permission ids.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "A role with that name already exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/roles/{roleId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        },
        {
          "name": "roleId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getRole",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "Get a role with its permissions and members",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The role.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Role"
                        },
                        {
                          "type": "object",
                          "properties": {
                            "permissions": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "members": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "additionalProperties": true
                              }
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "put": {
        "operationId": "updateRole",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "Update a role",
        "description": "Built-in system roles cannot be modified — only roles this organisation created.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RoleInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Over-long name, or unknown permission ids.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not an admin, or this is a system role.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "A role with that name already exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "delete": {
        "operationId": "deleteRole",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "Delete a role",
        "description": "Refused while anyone still holds the role — the count comes back in `details.memberCount`, so nobody silently loses access. Reassign those members first.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not an admin, or this is a system role.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Members still hold this role.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/roles/{roleId}/permissions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        },
        {
          "name": "roleId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "setRolePermissions",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "Replace a role's permissions",
        "description": "The array you send becomes the role's complete permission set — this is a replace, not an add.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "permissions"
                ],
                "properties": {
                  "permissions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Replaced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing array, or unknown permission ids.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/members/{memberId}/permissions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        },
        {
          "name": "memberId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getMemberPermissions",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "Read one member's effective access",
        "description": "Everything that decides what this member can do: their base role, the resolved permission list, individual grants and revocations, the custom roles they hold, and any per-document access.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The member's access.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "member": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string"
                            },
                            "role": {
                              "type": "string"
                            },
                            "isOwner": {
                              "type": "boolean"
                            }
                          }
                        },
                        "permissions": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "overrides": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PermissionOverride"
                          }
                        },
                        "customRoles": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Role"
                          }
                        },
                        "resourceAccess": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ResourceAccess"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/members/{memberId}/permissions/roles": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        },
        {
          "name": "memberId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "operationId": "setMemberRoles",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "Assign custom roles to a member",
        "description": "Replaces the member's custom roles with exactly the ids you send. An empty array removes them all. Every id must belong to this organisation.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "role_ids"
                ],
                "properties": {
                  "role_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Assigned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing array, or ids that are not roles of this organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/members/{memberId}/permissions/grant": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        },
        {
          "name": "memberId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "grantMemberPermission",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "Grant one permission to a member",
        "description": "An override on top of their roles, for the case that does not justify a whole new role. Set `expires_at` and it lapses on its own — useful for temporary access that would otherwise be forgotten.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "permission"
                ],
                "properties": {
                  "permission": {
                    "type": "string",
                    "description": "A permission id from the catalogue."
                  },
                  "expires_at": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true,
                    "description": "Must be in the future. Omit for a grant that does not expire."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Granted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No such member, or no such permission.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/members/{memberId}/permissions/revoke": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        },
        {
          "name": "memberId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "revokeMemberPermission",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "Deny one permission to a member",
        "description": "The mirror of `grant`: records a negative override that takes a permission away even though the member's role would give it. Use it to carve one capability out of an otherwise correct role.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "permission"
                ],
                "properties": {
                  "permission": {
                    "type": "string"
                  },
                  "expires_at": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/members/{memberId}/permissions/{permId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        },
        {
          "name": "memberId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "permId",
          "in": "path",
          "required": true,
          "description": "The override's id, from the member's `overrides` list.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "operationId": "deleteMemberPermissionOverride",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "Remove an override",
        "description": "Drops a grant or revocation, so the member falls back to what their roles say.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/members/{memberId}/permissions/resource-access": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        },
        {
          "name": "memberId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "grantResourceAccess",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "Give a member access to one template or submission",
        "description": "Access to a single document rather than a whole category — for the contractor who should see one contract and nothing else. `none` explicitly withholds a resource the member's role would otherwise reach.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resource_type",
                  "resource_id",
                  "access_level"
                ],
                "properties": {
                  "resource_type": {
                    "type": "string",
                    "enum": [
                      "template",
                      "submission"
                    ]
                  },
                  "resource_id": {
                    "type": "string",
                    "description": "Must belong to this organisation."
                  },
                  "access_level": {
                    "type": "string",
                    "enum": [
                      "view",
                      "edit",
                      "manage",
                      "none"
                    ]
                  },
                  "expires_at": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Granted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing fields, or an unknown resource type or access level.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No such member, or the resource is not in this organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/members/{memberId}/permissions/resource-access/{accessId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        },
        {
          "name": "memberId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "accessId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "operationId": "revokeResourceAccess",
        "tags": [
          "Roles & Permissions"
        ],
        "summary": "Remove per-document access",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/2fa/status": {
      "get": {
        "operationId": "get2faStatus",
        "tags": [
          "Two-Factor Auth"
        ],
        "summary": "Is 2FA on for your account?",
        "description": "`remainingRecoveryCodes` counts the unused ones. When it reaches zero, losing the authenticator means losing the account — regenerate before then.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Your 2FA state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    },
                    "enabledAt": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "remainingRecoveryCodes": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/2fa/enroll": {
      "post": {
        "operationId": "enroll2fa",
        "tags": [
          "Two-Factor Auth"
        ],
        "summary": "Start 2FA enrolment",
        "description": "Returns a TOTP URI to turn into a QR code, and the recovery codes. **The recovery codes are shown here and never again** — capture them before continuing.\n\nEnrolment is not finished until `POST /api/v1/2fa/verify` accepts a code from the authenticator, which proves the secret was actually stored somewhere.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 3,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Enrolment started.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "totpUri": {
                      "type": "string",
                      "description": "An `otpauth://` URI."
                    },
                    "recoveryCodes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "2FA is already enabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/2fa/verify": {
      "post": {
        "operationId": "verify2faEnrolment",
        "tags": [
          "Two-Factor Auth"
        ],
        "summary": "Finish 2FA enrolment",
        "description": "Confirms enrolment with a code from the authenticator. A code that has already been used is refused even while it is still within its time window, so an intercepted code cannot be replayed.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 5,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "2FA is now on.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No pending enrolment, a wrong code, or one already used.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/2fa/disable": {
      "post": {
        "operationId": "disable2fa",
        "tags": [
          "Two-Factor Auth"
        ],
        "summary": "Turn 2FA off",
        "description": "Needs a current TOTP code or an unused recovery code — so losing the authenticator does not lock you out permanently, and an unattended session cannot quietly remove your second factor.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 3,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "description": "A TOTP code or a recovery code."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "2FA is off.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "2FA is not on, or the code was wrong or already used.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/2fa/recovery-codes/regenerate": {
      "post": {
        "operationId": "regenerate2faRecoveryCodes",
        "tags": [
          "Two-Factor Auth"
        ],
        "summary": "Issue a fresh set of recovery codes",
        "description": "Invalidates every existing code and returns the replacements — once. Do this after using one, or if you suspect the old set was exposed.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 3,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "description": "A current TOTP code."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new codes. Store them now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "recoveryCodes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/2fa/org-policy": {
      "get": {
        "operationId": "get2faPolicy",
        "tags": [
          "Two-Factor Auth"
        ],
        "summary": "Read the organisation's 2FA policy",
        "description": "The organisation is taken from the `X-Organization-Id` header, or from the API token's own binding.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "parameters": [
          {
            "name": "X-Organization-Id",
            "in": "header",
            "required": false,
            "description": "Required for a session caller; an API token supplies its own organisation.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The policy.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "require2fa": {
                      "type": "boolean"
                    },
                    "gracePeriodDays": {
                      "type": "integer",
                      "description": "How long existing members have to enrol before it is enforced. Defaults to 7."
                    },
                    "enforcedAt": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "When the requirement was switched on; the grace period counts from here."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No organisation could be determined.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "put": {
        "operationId": "set2faPolicy",
        "tags": [
          "Two-Factor Auth"
        ],
        "summary": "Require 2FA for everyone in the organisation",
        "description": "Admins and owners. Existing members get `gracePeriodDays` to enrol before they are locked out — turning this on does not eject the team mid-day.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "parameters": [
          {
            "name": "X-Organization-Id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "require2fa"
                ],
                "properties": {
                  "require2fa": {
                    "type": "boolean"
                  },
                  "gracePeriodDays": {
                    "type": "integer",
                    "default": 7
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/2fa/trusted-devices": {
      "get": {
        "operationId": "listTrustedDevices",
        "tags": [
          "Two-Factor Auth"
        ],
        "summary": "List devices that skip the 2FA prompt",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Your trusted devices.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "devices": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "delete": {
        "operationId": "revokeAllTrustedDevices",
        "tags": [
          "Two-Factor Auth"
        ],
        "summary": "Stop trusting every device",
        "description": "The right move after losing a laptop: every device is asked for a code again on its next sign-in.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "All revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/2fa/trusted-devices/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "operationId": "revokeTrustedDevice",
        "tags": [
          "Two-Factor Auth"
        ],
        "summary": "Stop trusting one device",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/platform-admins": {
      "get": {
        "operationId": "listPlatformAdmins",
        "tags": [
          "Platform Admin"
        ],
        "summary": "List platform administrators",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The administrators.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "addPlatformAdmin",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Add a platform administrator",
        "description": "Platform owners only. `userId` accepts an email address as well as an id.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 5,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "userId",
                  "role"
                ],
                "properties": {
                  "userId": {
                    "type": "string",
                    "description": "A user id or an email address."
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "admin",
                      "owner"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Added.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No such user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already an administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/platform-admins/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "operationId": "updatePlatformAdmin",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Change an administrator's role",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "role"
                ],
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "admin",
                      "owner"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "removePlatformAdmin",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Remove an administrator",
        "description": "The last platform owner cannot be removed — there always has to be someone who can put the others back.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "That is the last platform owner.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/admin/billing/review-queue": {
      "get": {
        "operationId": "getBillingReviewQueue",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Billing events needing a human look",
        "description": "Platform admins only. Groups the last 30 days of billing events that did not settle cleanly, by organisation — the queue an operator works through.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The queue.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "orgs": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          }
                        },
                        "totalEvents": {
                          "type": "integer"
                        },
                        "windowDays": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/admin/organizations": {
      "get": {
        "operationId": "listAdminOrganizations",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Organizations (admin list)",
        "description": "Platform admins only, browser session only. One page of organizations with search, filters and sorting, plus counts per status for the same filters. `q` matches name, slug or an owner's email (contains; case-insensitive for A–Z only, other scripts match their exact case) or an exact organization id.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "past_due",
                "archived",
                "all"
              ],
              "default": "active"
            }
          },
          {
            "name": "plan",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "free",
                "starter",
                "pro",
                "business",
                "enterprise"
              ]
            }
          },
          {
            "name": "billing",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "credit_card",
                "bank_transfer"
              ]
            }
          },
          {
            "name": "agency",
            "in": "query",
            "description": "`direct` for organizations without an agency, or an agency id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "name",
                "created",
                "members",
                "plan"
              ],
              "default": "name"
            }
          },
          {
            "name": "dir",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000,
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "enum": [
                25,
                50,
                100
              ],
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The page.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "slug": {
                                "type": "string"
                              },
                              "createdAt": {
                                "type": "string"
                              },
                              "memberCount": {
                                "type": "integer"
                              },
                              "ownerEmail": {
                                "type": "string",
                                "nullable": true
                              },
                              "tier": {
                                "type": "string",
                                "enum": [
                                  "free",
                                  "starter",
                                  "pro",
                                  "business",
                                  "enterprise"
                                ]
                              },
                              "comped": {
                                "type": "boolean"
                              },
                              "billingMethod": {
                                "type": "string",
                                "enum": [
                                  "credit_card",
                                  "bank_transfer"
                                ],
                                "nullable": true
                              },
                              "subscriptionStatus": {
                                "type": "string",
                                "enum": [
                                  "active",
                                  "past_due",
                                  "canceled"
                                ],
                                "nullable": true
                              },
                              "agencyId": {
                                "type": "string",
                                "nullable": true
                              },
                              "agencyName": {
                                "type": "string",
                                "nullable": true
                              },
                              "archivedAt": {
                                "type": "string",
                                "nullable": true
                              }
                            }
                          }
                        },
                        "total": {
                          "type": "integer"
                        },
                        "page": {
                          "type": "integer"
                        },
                        "pageSize": {
                          "type": "integer"
                        },
                        "counts": {
                          "type": "object",
                          "properties": {
                            "all": {
                              "type": "integer"
                            },
                            "active": {
                              "type": "integer"
                            },
                            "pastDue": {
                              "type": "integer"
                            },
                            "archived": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_q, invalid_status, invalid_plan, invalid_billing, invalid_agency, invalid_sort, invalid_dir, invalid_page or invalid_pageSize.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "session_required (an API or MCP token was used) or Access denied (not a platform admin).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/admin/billing/partners-outbox": {
      "get": {
        "operationId": "getPartnersOutboxReview",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Partner events that need a person",
        "description": "Platform admins only. The partner-reporting outbox rows that did not go through: failed and not dismissed, or pending for over an hour (including rows held because the partners product key or binding is missing). Also returns the four health counts the hourly alarm uses. Rows carry ids, statuses and the organisation name, never the customer key; `customerKeyErased` is true once the 60-day scrub has erased it (a requeue restores it from the organisation's owner).",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The rows (oldest first, at most 200) and the health counts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "health": {
                          "type": "object",
                          "properties": {
                            "pending_overdue": {
                              "type": "integer"
                            },
                            "failed_open": {
                              "type": "integer"
                            },
                            "abandoned_24h": {
                              "type": "integer"
                            },
                            "missing_enqueued": {
                              "type": "integer"
                            }
                          }
                        },
                        "rows": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/admin/billing/partners-outbox/requeue": {
      "post": {
        "operationId": "requeuePartnersOutboxRow",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Send a failed or stuck partner event again",
        "description": "Platform admins only. Puts one failed or pending partner event back in the queue with its next attempt now and a fresh 30-day retry window. Audited with the admin's user id and the optional reason. A sale or sign-up whose customer key the 60-day retention scrub already erased gets it back from the organisation's owner record; it is refused when the organisation has no owner email (`no_customer_key`) or, for a sign-up, when that email changed since it was recorded (`email_changed`) — dismiss those instead.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "idemKey"
                ],
                "properties": {
                  "idemKey": {
                    "type": "string",
                    "description": "The row key, `<lead|sale|refund>:signflow:<externalId>`."
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Why. No customer personal data."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Requeued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Not a partner outbox key, or the reason is too long.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Not failed or pending (`wrong_status`), nothing to restore the customer key from (`no_customer_key`), the sign-up's email changed (`email_changed`), or changed meanwhile (`conflict`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/admin/billing/partners-outbox/dismiss": {
      "post": {
        "operationId": "dismissPartnersOutboxRow",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Close a partner event that will not be sent",
        "description": "Platform admins only. Closes one failed or pending partner event for good, recording who and why (the reason is required). Audited. The usual 60-day retention applies to its customer key.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "idemKey",
                  "reason"
                ],
                "properties": {
                  "idemKey": {
                    "type": "string",
                    "description": "The row key, `<lead|sale|refund>:signflow:<externalId>`."
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 200,
                    "description": "Why. No customer personal data."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dismissed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Not a partner outbox key, or a missing or over-long reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Not failed or pending (`wrong_status`), or changed meanwhile (`conflict`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/integrations/providers": {
      "get": {
        "operationId": "listConnectProviders",
        "tags": [
          "Connect"
        ],
        "summary": "List the systems SignFlow can connect to",
        "description": "Each manifest describes a provider and the credential fields it needs, so a connection form can be rendered without hard-coding anything.\n\nSignFlow Connect is an add-on. Where an organisation does not have it, the Connect endpoints answer 402 with `feature_locked`.\n\nAn API token needs a Connect scope on every Connect endpoint (each names its own in `x-required-scopes`): `integrations:read` to read, `integrations:write` to create, change, check, test or delete, and to see a connection's saved settings or a rule's inbound URL. `admin` holds both; a signed-in session is not scope-limited. The inbound webhook takes no API token.\n\nThe flow engine's HTTP app keeps its credentials (an API key or Basic Auth keychain, as Make calls it) as connections of provider `http`. That provider is not listed here: such a connection is made with `POST /api/v1/integrations/connections`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:read"
        ],
        "responses": {
          "200": {
            "description": "The available providers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/integrations/providers/{providerId}/credential-options": {
      "parameters": [
        {
          "name": "providerId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "An id from `GET /api/v1/integrations/providers`."
        }
      ],
      "post": {
        "operationId": "listConnectCredentialOptions",
        "tags": [
          "Connect"
        ],
        "summary": "List the choices for a credential field before connecting",
        "description": "Owner/admin only. Priority: `field: \"company\"` lists the companies (title + company code) read live with the credentials typed so far; a company must already be typed — Priority answers the list only under a valid company. Errors carry Priority's own text plus what to check; submitted values are never echoed.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "field",
                  "credentials"
                ],
                "properties": {
                  "field": {
                    "type": "string",
                    "description": "The credential field to list choices for (Priority: `company`)."
                  },
                  "credentials": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "The credentials typed so far, keyed by the provider manifest's credential fields."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The choices, sorted for display.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "value": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/integrations/connections": {
      "get": {
        "operationId": "listConnections",
        "tags": [
          "Connect"
        ],
        "summary": "List connections",
        "description": "Stored credentials are never serialised — neither the encrypted blob nor its IV leaves the server. `status` and `last_error` are how you tell whether a connection is still working.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:read"
        ],
        "responses": {
          "200": {
            "description": "The connections.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Connection"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "createConnection",
        "tags": [
          "Connect"
        ],
        "summary": "Connect a system",
        "description": "Credentials are verified against the provider before anything is stored, so a connection that saves is a connection that works.\n\n`instanceUrl` is checked for reachability first — a host that resolves to a private or internal address is refused, which stops a connection form being turned into a probe of SignFlow's own network. Error messages coming back from the provider are scrubbed of anything you submitted, so a mistyped password is never echoed.\n\nAn HTTP app keychain (provider `http`) is the exception: Make does not verify a key, so nothing is sent anywhere and only the shape of the credentials is checked. Owner or admin only, as every connection.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "provider"
                ],
                "properties": {
                  "provider": {
                    "type": "string",
                    "description": "An id from `GET /api/v1/integrations/providers`."
                  },
                  "instanceUrl": {
                    "type": "string",
                    "format": "uri",
                    "description": "The customer's own installation, where the provider is self-hosted."
                  },
                  "credentials": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Keyed by the provider manifest's credential fields. For Priority these are `host`, `ini`, `company`, `language`, optional `servicePath`, `pat` and optional `appId`/`appKey`, and a `serviceRoot` not shaped `https://<server>[/<prefix>]/odata/Priority/<file>.ini[,<n>]/<company>` is refused. For `http` (the flow engine's HTTP app), `kind` is `apiKey` or `basic`. An API key takes `name` (the header or query parameter name, for example `x-api-key`), `value` (the full key, including any prefix such as `Bearer `) and `placement` (`header` or `query`). Basic Auth takes `username` and `password`, sent as `Authorization: Basic base64(username:password)`; either may be empty, not both. `kind: oauth2` is answered 422 `sign_in_required`."
                  },
                  "displayName": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Connected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "provider": {
                          "type": "string"
                        },
                        "display_name": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown provider (a flow app your organization cannot use is answered the same way), an OAuth provider that does not take a credential form, an address that cannot be reached safely, or credentials the provider rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "The flow app signs in through the browser, at the provider's own page (`code` is `sign_in_required`): it is connected only with `POST /api/v1/integrations/oauth/start` (a signed-in session only), never with credentials. An HTTP app connection (`http`) of `kind` `oauth2` is answered the same way: OAuth 2.0 signs in through the browser only. Nothing is sent to the provider and nothing is stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/integrations/connections/{id}/usage": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getConnectionUsage",
        "tags": [
          "Connect"
        ],
        "summary": "List the flows that use a connection",
        "description": "Each module of your flows (drafts or the versions they run; the trash left out) that uses this connection, at most 1,000 (`truncated` says there were more). Needs flows to be enabled for your organization.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:read"
        ],
        "responses": {
          "200": {
            "description": "The modules.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ConnectionUsage"
                      }
                    },
                    "truncated": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/integrations/connections/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "operationId": "deleteConnection",
        "tags": [
          "Connect"
        ],
        "summary": "Remove a connection",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:write"
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/integrations/connections/{id}/credentials": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getConnectionCredentials",
        "tags": [
          "Connect"
        ],
        "summary": "Read a connection's non-secret settings",
        "description": "Owner/admin only. Returns only fields the provider marks non-secret (Priority: server address, OData path, .ini, language, company, App ID; an HTTP app keychain: its `kind`, an API key's parameter `name` and `placement`, a Basic Auth `username`). The access token and App key are never returned, nor an HTTP key or password. They are still a credential's settings, so an API token needs `integrations:write` to read them, as it does to change them; `integrations:read` is not enough.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:write"
        ],
        "responses": {
          "200": {
            "description": "The non-secret settings, keyed by the provider manifest's credential fields.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "values": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "The connection's app is no longer available (`code` is `provider_unavailable`): it can only be removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "updateConnectionCredentials",
        "tags": [
          "Connect"
        ],
        "summary": "Update a connection's credentials in place",
        "description": "Rotates the stored credentials on the SAME connection, so every flow and queued write-back on it keeps working. Owner or admin only. The new credentials are verified live against the provider before anything is stored, exactly as when connecting, and a successful update resets the connection to `active`.\n\nAn HTTP app keychain (`http`) is only checked for shape: nothing is sent.\n\nThe new credentials must address the same remote account (for Priority: the same server and company; for an HTTP app keychain: the same `kind`, since a keychain's type is fixed when it is made). Credentials for another account are refused with `409` and `code: different_account` — add those as a new connection instead. A removed connection cannot be updated (`409`) — connect again.\n\nError messages are scrubbed of anything you submitted.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "credentials"
                ],
                "properties": {
                  "credentials": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Keyed by the provider manifest's credential fields. For Priority these are `host`, `ini`, `company`, `language`, optional `servicePath`, `pat` and optional `appId`/`appKey`; a `serviceRoot` not shaped `https://<server>[/<prefix>]/odata/Priority/<file>.ini[,<n>]/<company>` is refused, and a blank `appKey` with an unchanged `appId` keeps the stored App key. For `http`, the fields `POST /api/v1/integrations/connections` takes, all of them again."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "active"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The connection was removed, or the credentials are for a different account (`code: different_account`; for an HTTP app keychain, another `kind`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "different_account"
                      ]
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The connection's app is no longer available to your organization (`code` is `provider_unavailable`), or it signs in through the browser (`code` is `sign_in_required`): such an app is signed in only at the provider's own page, starting with `POST /api/v1/integrations/oauth/start` (a signed-in session only; it makes a new connection), never with credentials. So are HTTP app credentials of `kind` `oauth2` (`sign_in_required`). Nothing is sent to the provider and nothing is changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/integrations/connections/{id}/refresh-schema": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "refreshConnectionSchema",
        "tags": [
          "Connect"
        ],
        "summary": "Drop the cached schema",
        "description": "Forces the next discovery call to ask the provider again. Use it after adding a field on the other side — otherwise the cached list is what you keep seeing.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:write"
        ],
        "responses": {
          "200": {
            "description": "Cache cleared.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "ok": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/integrations/connections/{id}/objects": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listConnectionObjects",
        "tags": [
          "Connect"
        ],
        "summary": "Discover the objects on the far side",
        "description": "What the connected system holds — forms, entities, tables, whatever that provider calls them. The starting point for building a rule.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:read"
        ],
        "parameters": [
          {
            "name": "include",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated object ids a saved flow already uses (at most 10). Any the provider no longer offers is returned with \"unavailable\": true and its best-known title."
          }
        ],
        "responses": {
          "200": {
            "description": "The objects.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          },
                          "group": {
                            "type": "string"
                          },
                          "unavailable": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "id",
                          "label"
                        ]
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "notice": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "unfiltered",
                            null
                          ],
                          "description": "\"unfiltered\": the provider could not say which objects this user may use, so all are listed."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "More than 10 include ids.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "The connection has no records: it is an app pack's or an HTTP app keychain (`http`), which flows use (`code` is `no_records`); or its app is no longer available (`code` is `provider_unavailable`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The provider could not be reached, or refused the request. The message is scrubbed of anything sensitive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/integrations/connections/{id}/objects/{objectId}/fields": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "objectId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listObjectFields",
        "tags": [
          "Connect"
        ],
        "summary": "Discover an object's fields",
        "description": "The columns you can map onto template fields. `meta.fromCache` tells you whether this came from the cached schema or a fresh look at the provider.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:read"
        ],
        "parameters": [
          {
            "name": "expand",
            "in": "query",
            "required": false,
            "description": "A subform to pull in alongside the object's own fields, for providers that nest one record inside another.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "refresh",
            "in": "query",
            "required": false,
            "description": "Bypass the cache for this call.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The fields.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "fromCache": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The named subform is not on this object — it may have been renamed on the provider side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "The connection has no records: it is an app pack's or an HTTP app keychain (`http`), which flows use (`code` is `no_records`); or its app is no longer available (`code` is `provider_unavailable`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Discovery failed at the provider.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/integrations/connections/{id}/objects/{objectId}/targets": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "objectId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listObjectTargets",
        "tags": [
          "Connect"
        ],
        "summary": "Discover where results can be written back",
        "description": "The places on the far side a rule may write to once a document is signed — an attachment slot, a status field, a related record. An empty list means this object accepts no write-back.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:read"
        ],
        "parameters": [
          {
            "name": "refresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The write-back targets.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "The connection has no records: it is an app pack's or an HTTP app keychain (`http`), which flows use (`code` is `no_records`); or its app is no longer available (`code` is `provider_unavailable`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Discovery failed at the provider.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/integrations/connections/{id}/objects/{objectId}/records": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "objectId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "searchObjectRecords",
        "tags": [
          "Connect"
        ],
        "summary": "Find a record to test a flow with",
        "description": "Searches the metadata key columns. Results are newest first only when the provider declares a creation-order column (`newestFirst`). `recordId` is the id `POST /rules/{id}/dry-run` takes as `record_id`. Requires the integrations.rules.edit permission (like dry-run).",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:read"
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Matched as \"contains\" in the record's key text columns and the optional `in` column. At most 60 characters are kept. Empty lists the newest records.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "in",
            "in": "query",
            "required": false,
            "description": "One text column id to search in as well.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Up to ten matching records.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "records",
                        "columns",
                        "newestFirst",
                        "searchable"
                      ],
                      "properties": {
                        "records": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "recordId": {
                                "type": "string"
                              },
                              "values": {
                                "type": "object",
                                "additionalProperties": {
                                  "type": [
                                    "string",
                                    "number",
                                    "null"
                                  ]
                                }
                              }
                            }
                          }
                        },
                        "columns": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "label": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "newestFirst": {
                          "type": "boolean"
                        },
                        "searchable": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The form declares no key columns, more than one `in` column was given, the extra column is not a text column of it, or the provider cannot search records.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "The connection has no records: it is an app pack's or an HTTP app keychain (`http`), which flows use (`code` is `no_records`); or its app is no longer available (`code` is `provider_unavailable`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The search failed at the provider.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/integrations/connections/{id}/objects/{objectId}/events": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "objectId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listObjectEvents",
        "tags": [
          "Connect"
        ],
        "summary": "Discover what can trigger a rule",
        "description": "The events this object can raise — a record created, a status reached — for use as a rule's trigger.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:read"
        ],
        "responses": {
          "200": {
            "description": "The available triggers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "The connection has no records: it is an app pack's or an HTTP app keychain (`http`), which flows use (`code` is `no_records`); or its app is no longer available (`code` is `provider_unavailable`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Discovery failed at the provider.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/integrations/rules": {
      "get": {
        "operationId": "listConnectRules",
        "tags": [
          "Connect"
        ],
        "summary": "List rules",
        "description": "A rule ties a trigger on the connected system to a SignFlow template: when this happens over there, send this document.\n\n`inbound_token` is deliberately absent from this list. It is a bearer secret, and a list response is exactly the payload that ends up in a cache, a HAR file, or a debug dump. Read it from `GET /api/v1/integrations/rules/{id}/inbound-url`, which exists for that one purpose.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:read"
        ],
        "responses": {
          "200": {
            "description": "The rules, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ConnectRule"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "createConnectRule",
        "tags": [
          "Connect"
        ],
        "summary": "Create a rule",
        "description": "A new rule is created inactive, and its trigger is **not** validated at this point — you are expected to build it up over several calls, and a half-built rule that refuses to save would make that impossible. Validation happens in full at activation, which is the moment it starts to matter.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConnectRuleInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created, inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "The connection or template does not exist in this organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The connection has no records (an app pack's or an HTTP app keychain, which flows use), so no rule can use it (`code` is `no_records`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/integrations/rules/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "patch": {
        "operationId": "updateConnectRule",
        "tags": [
          "Connect"
        ],
        "summary": "Update a rule",
        "description": "Partial. As with create, an inactive rule accepts an incomplete trigger; an active one is validated on the spot and a change that would break it is refused with a 400 whose `error` is the first problem and whose `blockers` array lists all of them.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConnectRuleInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "updated": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed request, or a trigger change that would break an ACTIVE rule. For the latter, `error` is the first problem and `blockers` is all of them; any other 400 carries `error` alone.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "blockers": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Every problem that blocks the change. Present only when an active rule's trigger was refused."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "The connection has no records (an app pack's or an HTTP app keychain, which flows use), so no rule can use it (`code` is `no_records`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/integrations/rules/{id}/activate": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "activateConnectRule",
        "tags": [
          "Connect"
        ],
        "summary": "Activate a rule",
        "description": "Validates the whole rule — trigger, template, field and signer mapping, write-back — and only then starts it. Anything unresolved comes back as a 400 whose `blockers` array lists every problem at once, rather than making you fix them one call at a time.\n\nFor a push-style rule, activation is also what mints the inbound token.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:write"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "includeExisting": {
                    "type": "boolean",
                    "description": "First-match (\"once per matching record\") flows only: the answer to a 409 `baseline_confirm` — `false` = only react to new matches (the matching records are recorded as skipped runs), `true` = also send to the records that match right now. Flows that watch for changes always start from now and never need it; sending `true` there skips the silent baseline (an escape hatch for recovery)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Active.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "is_active": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The rule is not complete. `error` is the first problem, `blockers` is all of them. Also `code: baseline_over_cap` when more than 500 records match right now — first-match (\"once per matching record\") flows only; narrow the trigger's condition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "blockers": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "baseline_over_cap"
                      ]
                    },
                    "matching": {
                      "type": "integer",
                      "description": "With `baseline_over_cap`: how many records match the trigger right now."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "First-match (\"once per matching record\") flows only: records already match this trigger — re-send with `includeExisting`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "baseline_confirm"
                      ]
                    },
                    "matching": {
                      "type": "integer",
                      "description": "How many records match the trigger right now."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The connection has no records (an app pack's or an HTTP app keychain, which flows use), so no rule can use it (`code` is `no_records`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The provider refused to register the subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/integrations/rules/{id}/deactivate": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "deactivateConnectRule",
        "tags": [
          "Connect"
        ],
        "summary": "Deactivate a rule",
        "description": "Stops it firing. The rule and its mapping are kept, so it can be switched back on unchanged.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:write"
        ],
        "responses": {
          "200": {
            "description": "Inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "is_active": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/integrations/rules/{id}/dry-run": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "dryRunConnectRule",
        "tags": [
          "Connect"
        ],
        "summary": "Test a rule against one real record",
        "description": "Runs the whole rule against a record you name and shows what it would produce — resolved field values, signers, and each step of the flow — without creating a submission or emailing anyone. The fastest way to find a mapping mistake before it reaches a customer.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "record_id"
                ],
                "properties": {
                  "record_id": {
                    "type": "string",
                    "description": "A record id on the connected system."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What the rule would have done.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true,
                      "properties": {
                        "flow": {
                          "type": "object",
                          "properties": {
                            "steps": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "additionalProperties": true
                              }
                            },
                            "blockers": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "description": "The record could not be fetched from the provider.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/integrations/rules/{id}/suggest-mapping": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "suggestConnectMapping",
        "tags": [
          "Connect"
        ],
        "summary": "Propose a field mapping",
        "description": "Matches the object's fields against the template's and suggests pairings. A starting point to correct, not a mapping to trust unread — nothing is saved until you send it back through `PATCH /api/v1/integrations/rules/{id}`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:write"
        ],
        "responses": {
          "200": {
            "description": "The suggestions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The rule has no template selected yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "description": "Discovery failed at the provider.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/integrations/rules/{id}/runs": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listConnectRuleRuns",
        "tags": [
          "Connect"
        ],
        "summary": "Rule run history",
        "description": "Every time the rule fired, and what came of it.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:read"
        ],
        "responses": {
          "200": {
            "description": "The runs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ConnectRuleRun"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/integrations/rules/{id}/inbound-url": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getConnectInboundUrl",
        "tags": [
          "Connect"
        ],
        "summary": "Reveal a push rule's inbound URL",
        "description": "The address the connected system posts to, and the bearer token in it. This is the only endpoint that returns the token — it is left out of the rule list on purpose. The token is a credential (whoever holds it can start the rule), so an API token needs `integrations:write` to see it; `integrations:read` is not enough.\n\nOnly meaningful for a push-style rule, and only after activation, which is when the token is minted.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:write"
        ],
        "responses": {
          "200": {
            "description": "The inbound endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "token": {
                          "type": "string",
                          "description": "Treat as a secret."
                        },
                        "path": {
                          "type": "string",
                          "description": "Append to the API base to get the full URL."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Not a push rule, or not activated yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/integrations/inbound/{token}": {
      "parameters": [
        {
          "name": "token",
          "in": "path",
          "required": true,
          "description": "The bearer token from `GET /api/v1/integrations/rules/{id}/inbound-url`.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "receiveInboundEvent",
        "tags": [
          "Connect"
        ],
        "summary": "Receive a push from a connected system",
        "description": "Called by the customer's own system, not by you. The token in the path is the whole credential, which is why it is revealed by one dedicated endpoint and kept out of every list.\n\nThe payload is queued and answered `202` straight away — the rule runs afterwards. A `202` therefore means accepted, not completed; watch the run history for the outcome.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "description": "Whatever the provider sends. It must identify a record, or the call is refused."
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted and queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "queued": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "No rule answers to that token. Deliberately indistinguishable from a revoked one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The payload is too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The payload does not identify a record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The payload could not be processed as this provider's shape.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/integrations/jobs/dead": {
      "get": {
        "operationId": "listDeadConnectJobs",
        "tags": [
          "Connect"
        ],
        "summary": "List jobs that gave up",
        "description": "Rule runs that failed past their retries. Each carries why, so the fix can be made before replaying.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:read"
        ],
        "responses": {
          "200": {
            "description": "The dead-lettered jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/integrations/jobs/{id}/replay": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "replayConnectJob",
        "tags": [
          "Connect"
        ],
        "summary": "Replay a dead job",
        "description": "Re-runs it with the payload it originally received — for after the cause has been fixed. A job that is not in a replayable state answers 404 rather than silently doing nothing.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "integrations:write"
        ],
        "responses": {
          "200": {
            "description": "Queued for replay.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/integrations/oauth/start": {
      "post": {
        "operationId": "startBrowserSignIn",
        "tags": [
          "Connect"
        ],
        "summary": "Start signing in to an app in the browser",
        "description": "For a flow app whose connection is made by signing in at the provider's own page (an OAuth 2 authorization with a browser step, such as Google or Microsoft). A signed-in session only: an API token is refused with `session_required`, whatever its scopes, `admin` included, because a browser sign-in is a person's. Only an owner or admin of the organization may start one, as for `POST /api/v1/integrations/connections`, and only for an app the organization may use (an app it may not use is answered as an unknown provider).\n\nSignFlow makes the PKCE code verifier and the `state` itself and keeps a record of the sign-in on the server for 10 minutes: only a hash of the `state` is stored, and the verifier is stored encrypted. The answer is the address of the provider's sign-in page; open it in the same browser. When the person has signed in, the provider sends the browser to `GET /api/v1/integrations/oauth/callback`, which sends it on to SignFlow's `/connections/oauth/finish` page, which calls `POST /api/v1/integrations/oauth/finish`.\n\n`parameters` are the connection's own fields, as the app declares them (never a secret field, nor a list or a group of fields that holds one: one is refused). The address the provider returns to is fixed for the whole service (`https://signflow-api.leonai.io/api/v1/integrations/oauth/callback` in production) and never taken from the request.\n\nTo sign an existing connection in again (Reauthorize: its sign-in was revoked or expired at the provider), pass its id as `connectionId`. It must be a connection of the same organization and the same app that has not been deleted; the sign-in then updates that connection in place (it keeps its id, so every flow on it keeps working) instead of creating one. The account is compared when the app's sign-in reports the account's id (`uid`) and the connection holds one: another account is answered 409 `different_account` by `POST /api/v1/integrations/oauth/finish` and nothing is changed; otherwise the sign-in is accepted, as `PUT /api/v1/integrations/connections/{id}/credentials` accepts it. Without `parameters`, the sign-in reuses the connection's own non-secret fields exactly as they are stored, each with its type: a number, a `true` or `false`, a list or a group of fields is kept as it is (`GET /api/v1/integrations/connections/{id}/credentials` shows only the text, number and `true`/`false` ones, as text); with them, the given ones are used instead. Either way they are checked as given ones are.\n\n**The HTTP app's OAuth 2.0 connection.** Instead of `packId`, pass `\"app\": \"http\"` to connect the flow engine's HTTP app with OAuth 2.0 (its \"OAuth 2.0\" authentication type) using your own OAuth client, the one you registered with your provider, never one of SignFlow's: register the address above as its redirect URI. `parameters` are the client's non-secret settings: `authorizeUri` and `tokenUri` (https addresses; private, internal and LeonAI's own addresses are refused), `clientId`, `scopes` (a list, or one text split at blanks and commas), `scopeSeparator` (`SPACE`, the default, or `COMMA`), `clientAuthentication` (`client_secret_post`, the default: the client id and secret in the token request's body; or `client_secret_basic`: in an HTTP Basic header) and `flowType` (`authorizationCode`, the only flow: `implicit` is refused). Any other parameter is refused as not available. The client secret goes in `clientSecret`, never among the parameters (omit it for a public client), and the connection's name in `displayName`. The sign-in uses the Authorization Code flow with PKCE (S256) always. The secret is kept encrypted with the sign-in's record and then in the connection, and is never shown again. Only for an organization with the flow engine (otherwise answered as an unknown provider). A Reauthorize (`connectionId`) names an HTTP OAuth 2.0 connection; without `parameters` it reuses the connection's own client, and without `clientSecret` it keeps the stored secret unless the client ID or the token URI changed, which needs the secret again.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "parameters": [
          {
            "name": "organization_id",
            "in": "query",
            "required": true,
            "description": "The organization the connection is for.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Either `packId` (an app pack) or `app` (the HTTP app), never both.",
                "properties": {
                  "packId": {
                    "type": "string",
                    "maxLength": 100,
                    "description": "The app's id (the part after `pack:` of its provider id). Required unless `app` is given."
                  },
                  "app": {
                    "type": "string",
                    "enum": [
                      "http"
                    ],
                    "description": "`http`: an OAuth 2.0 connection of the flow engine's HTTP app, with your own OAuth client (see above). Instead of `packId`."
                  },
                  "clientSecret": {
                    "type": "string",
                    "maxLength": 8192,
                    "nullable": true,
                    "description": "With `app: \"http\"` only: your OAuth client's secret. Empty for a public client. Omitted or null: none for a new connection; on a Reauthorize, the stored one is kept for the same client ID and token URI."
                  },
                  "displayName": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "With `app: \"http\"` only: the new connection's name. Default `HTTP`."
                  },
                  "parameters": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "With `app: \"http\"`: your OAuth client's non-secret settings (`authorizeUri`, `tokenUri`, `clientId`, `scopes`, `scopeSeparator`, `clientAuthentication`, `flowType`; see above). Otherwise: the connection's non-secret fields, keyed by field name (a list or a group of fields that holds a secret field is secret too). Empty fields are left out; a required one missing is refused. Omitted on a Reauthorize (with `connectionId`): the connection's own non-secret fields are reused exactly as they are stored, each with its type: a number, a `true` or `false`, a list or a group of fields is kept as it is (`GET /api/v1/integrations/connections/{id}/credentials` shows only the text, number and `true`/`false` ones, as text)."
                  },
                  "connectionId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100,
                    "nullable": true,
                    "description": "Optional. The id of an existing connection of this app to sign in again (Reauthorize) instead of creating a new one. Absent or null: a new connection."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The provider's sign-in page.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "url"
                      ],
                      "properties": {
                        "url": {
                          "type": "string",
                          "format": "uri"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body (a `connectionId` that is not a connection id included), an unknown provider (an app your organization cannot use is answered the same way), an app that connects with a credential form instead (`not_browser_sign_in`), a missing or secret field, or the provider's page could not be prepared. For the HTTP app: the Implicit flow, an authorize or token URI that cannot be used, a parameter that is not available, the client secret among the parameters, or a Reauthorize whose client ID or token URI changed without the secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`connectionId` names no connection of this organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`connectionId` names a connection that was deleted: connect again instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The app's browser sign-in is not set up on this service (`oauth_client_missing`), `connectionId` names a connection of another app (`different_app`), or, for the HTTP app, a keychain rather than an OAuth 2.0 connection (`not_oauth2`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "description": "The app gave a sign-in address that cannot be opened.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Browser sign-in is not configured on this server (`not_configured`). Nothing was written.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/integrations/oauth/callback": {
      "get": {
        "operationId": "browserSignInCallback",
        "tags": [
          "Connect"
        ],
        "summary": "The provider's return from a browser sign-in (browser endpoint)",
        "description": "Not called from code: the provider sends the person's browser here after they sign in, and this is the one address registered with providers. It takes no authentication and no API token: it is authorised by the sign-in's `state` alone, which works once, for 10 minutes.\n\nIt does not exchange the code itself. It keeps the provider's answer (its query, at most 20 parameters of at most 4 KiB each) encrypted with the sign-in's record, and sends the browser to the SignFlow page the sign-in started from (`/connections/oauth/finish`) with a one-time finish token in the URL fragment, never in the query. A provider error ends the sign-in and is passed on as a short `error` code only. An unknown, expired or already used `state` sends the browser to SignFlow with `error=expired`. The destination is never taken from the request.",
        "security": [],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "parameters": [
          {
            "name": "state",
            "in": "query",
            "required": true,
            "description": "The `state` SignFlow sent the provider.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code",
            "in": "query",
            "required": false,
            "description": "The provider's authorization code.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "error",
            "in": "query",
            "required": false,
            "description": "The provider's error code, when the sign-in failed or was declined.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "To the SignFlow finish page: with `#finish=<token>` after a sign-in, or `?error=<code>` (`expired`, `invalid_callback`, or the provider's code when it is short and plain, else `provider_error`)."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/integrations/oauth/finish": {
      "post": {
        "operationId": "finishBrowserSignIn",
        "tags": [
          "Connect"
        ],
        "summary": "Finish signing in to an app in the browser",
        "description": "Called by SignFlow's `/connections/oauth/finish` page with the finish token from its URL fragment. A signed-in session only (an API token is refused with `session_required`), and it must be the session of the person who started the sign-in: anyone else's is refused with the same 404 as an unknown token, the sign-in is ended, and the attempt is recorded in the organization's audit log. The token works once, for 2 minutes.\n\nEverything is checked again for the organization the sign-in was started in (never one the request names): the person is still an owner or admin there, the organization still has SignFlow Connect and may still use the app, and the app's sign-in is still set up. Only then is the provider's code exchanged. The connection is stored encrypted, named after the account it reached, and recorded in the audit log, as `POST /api/v1/integrations/connections` stores one; the answer shows it as `GET /api/v1/integrations/connections` does. Errors coming back from the provider are scrubbed of the code, the verifier and every secret.\n\nA sign-in started with a `connectionId` (Reauthorize) updates that connection in place instead: it is read again first, and one deleted since is answered 404 before the provider is reached. The account is compared when the app's sign-in reports the account's id (`uid`) and the connection holds one: another account is answered 409 `different_account` and nothing is changed (add it as a new connection instead); without one, the sign-in is accepted, as `PUT /api/v1/integrations/connections/{id}/credentials` accepts it. An accepted sign-in's new credentials replace the old ones as `PUT /api/v1/integrations/connections/{id}/credentials` replaces them: the connection is active again, its failure count and last error are cleared, and the change is recorded in the audit log together with it. The connection keeps its id, so every flow on it keeps working.\n\nFor the HTTP app's OAuth 2.0 connection (`app: \"http\"` at start), the organization must still have the flow engine, and the code is exchanged at your client's token URI with one POST (form-encoded: the code, the redirect URI, the PKCE verifier, and the client's authentication as chosen), checked again against private, internal and LeonAI's own addresses; a redirect from the token endpoint is not followed. The exchange, like every renewal, counts as one of the organization's outbound requests (1,000 per 60 seconds, shared with the HTTP app's own requests): when they are spent, nothing is sent and the answer is 429 with `Retry-After`; connect again after it. The connection's provider is `http`; its client, secret and tokens are stored encrypted with it and never shown. When your provider issues a refresh token, the access token is renewed with it when it is due within five minutes, and once when a request is refused with 401; without one, reauthorize the connection (it keeps its id). A renewal the token endpoint refuses counts toward the connection's failures, as a refused request after a renewal does.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "The finish token from the page's URL fragment."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in again (Reauthorize): the same connection, updated in place.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Connection"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Connected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Connection"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "An unknown provider (an app the organization can no longer use), an app that no longer signs in through the browser (`not_browser_sign_in`), the provider refused the code, returned none, or answered with a redirect or something that is not a token, or the sign-in could not be read: connect again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "The sign-in expired, was already finished, or was started by someone else; or, for a Reauthorize, the connection was deleted since the sign-in started.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A Reauthorize reached a different account than the connection's (`different_account`). Nothing was changed: add it as a new connection instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The app's browser sign-in is no longer set up on this service (`oauth_client_missing`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests: this endpoint's own limit (see `x-rate-limit`), or, for the HTTP app's OAuth 2.0 connection, the organization's outbound requests (1,000 per 60 seconds) are spent, and nothing was sent to the token endpoint. `Retry-After` says when to connect again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/flows": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listFlows",
        "tags": [
          "Flows"
        ],
        "summary": "List flows",
        "description": "Live flows, most recently changed first. With `trashed=1`, the flows in the trash instead, most recently trashed first; a flow leaves the trash for good 30 days after it was trashed.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "trashed",
            "in": "query",
            "required": false,
            "description": "`1` lists the trash.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 200; default 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "The flows, without their graphs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "folder": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "is_active": {
                            "type": "boolean"
                          },
                          "draft_rev": {
                            "type": "integer"
                          },
                          "draft_updated_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "published_version_id": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "published_version_no": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "trashed_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "created_by": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "last_run": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "description": "The flow's newest run, the one `GET /api/v1/flows/{id}/runs` lists first (check runs and retries included); null when it has never run.",
                            "properties": {
                              "status": {
                                "type": "string",
                                "enum": [
                                  "running",
                                  "waiting",
                                  "success",
                                  "warning",
                                  "error",
                                  "stopped"
                                ]
                              },
                              "started_at": {
                                "type": "string",
                                "format": "date-time"
                              }
                            }
                          },
                          "path": {
                            "$ref": "#/components/schemas/FlowPath"
                          },
                          "module_labels": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "The labels of the flow's modules, for search: each module's own, else the module's name in English and Hebrew. At most 100."
                          },
                          "runs_7d": {
                            "type": "array",
                            "items": {
                              "$ref": "#/components/schemas/FlowRunsDay"
                            },
                            "description": "The last 7 days, oldest first."
                          },
                          "operations_30d": {
                            "type": "integer",
                            "description": "Operations of the flow's runs that started in the last 30 days."
                          },
                          "open_incomplete": {
                            "type": "integer",
                            "description": "Runs saved to retry that are pending or retrying."
                          },
                          "open_waits": {
                            "type": "integer",
                            "description": "Documents waiting for signers."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "createFlow",
        "tags": [
          "Flows"
        ],
        "summary": "Create a flow",
        "description": "Creates an inactive flow whose draft is `graph`, or an empty graph when `graph` is left out. Nothing is published until the first save. Needs the `flows.edit` permission. A note on a module or a link may carry `by` (the user id of who wrote it) and `at` (when, in UTC, as `2026-10-16T09:30:00Z`), set by the builder: a display aid, not an audit record. A `by` that names no member of this organisation is dropped (one unchanged from the saved draft stays, so a former member keeps their byline), and the note is kept.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "folder": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "graph": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "A version 3 flow graph: modules, links, settings, interface and meta. At most 1,900,000 bytes as JSON."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new flow, as `GET /api/v1/flows/{id}` returns it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, a bad name or folder, or a graph that does not parse (`invalid_graph`, with `errors`: a list of `{ path, message }`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "description": "The graph or the request body is too large (`graph_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/flows/apps": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listFlowApps",
        "tags": [
          "Flows"
        ],
        "summary": "List the apps a flow can use",
        "description": "Read-only. The apps this organisation may use in a flow now, by id: the built-in apps, and each app pack its status lets this organisation see (an `alpha` pack for the organisations the platform owner chose, a `beta` pack for the organisations enabled for it, a `ga` pack for every organisation with the flow engine; an `off` pack for none). Each app lists its modules (their type, label, whether they write, and the shape validation checks: `kind`, `canFail`, and `trigger`, `placement` or `needsIncompleteRuns` when they apply; and `ui`, how a flow builder shows the module, when it defines one) and this organisation's connections of that app, as `GET /api/v1/integrations/connections` shows them: never a credential, never another organisation's. An app carries `ui` too (its name in Hebrew and English, its colour and its icon) when it defines one; an app or a module that defines none has no `ui` field. A module withheld from the modules a builder offers (marked hidden by its app) is not listed, and an app whose every module is withheld is not listed either; such a module is still a known type wherever a flow is validated, a flow that has one still runs, and `POST /api/v1/flows/schemas` still answers its forms. The connections are there only when the caller may read Connect connections (a signed-in session, or an API token with `integrations:read` or `admin`); for an API token without either, every app comes without its `connections` field (absent, not empty), and nothing else changes. A module of an app this organisation may not see is an unknown type (`unknown_module_type`) wherever a flow is validated, and a run of a flow that uses one cannot start. The flow-control modules also carry Make's id and name (`makeId`, `makeName`), and an error handler a line on how the run ends (`ui.outcome`). An app may carry, in its `ui`, a description, a category, how it connects (`authKinds`), setup steps and what its connection allows (`scopeWords`), and a trigger what its items are called (`ui.itemNoun`); each only where the app gives it.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The apps.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FlowApp"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/flows/apps/next": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getFlowLikelyNext",
        "tags": [
          "Flows"
        ],
        "summary": "Get the modules that most often follow a module",
        "description": "Read-only, for a flow builder's Add a module. The top 3 module types that directly follow `after` in this organisation's own flows: a link from a module of that type to the next, a route, an if or else branch, or a signed or declined path (an error route does not count). Both a flow's draft and its published version are read, and a flow counts once however many of them have the pair; flows in the trash are not read. Each entry says in how many flows. `flows_with_after` is how many of the organisation's flows have the `after` module at all, so a builder can show the suggestion only when there is enough to go on. Only modules this organisation may add after a module are listed: never a trigger, a merge or an error handler, and never one that is hidden or that this organisation may not use. An `after` this organisation may not use answers empty, as an unused one does. Another organisation's flows are never read, and no flow id, name or graph is in the answer. Needs membership; nothing is written.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "after",
            "in": "query",
            "required": true,
            "description": "A module type, like `tools:Sleep`.",
            "schema": {
              "type": "string",
              "maxLength": 256
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The likely next modules.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowLikelyNext"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`after` is missing or is not a module type (`invalid_after`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/flows/schemas": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "getFlowModuleSchemas",
        "tags": [
          "Flows"
        ],
        "summary": "Get the forms of modules",
        "description": "Read-only, for a flow builder. For each node sent (1 to 300, each id once), its module's settings (`config`), its mapped inputs for those settings (`input`) and its output (`output`), each field with the builder's `ui` where the module defines one; the answer is keyed by node id, as text. The schemas come from the apps this organisation may use now, as `GET /api/v1/flows/apps` lists them, except that a module the catalog withholds still answers (a flow that has one still validates and runs). Each node is read as a module of a saved flow is. One that does not parse (a type that is not `app:Module` or is longer than 256 characters, settings that are not an object or nest too deep, a mapping that is not a valid value, an empty connection id, or a field other than `id`, `type`, `config`, `map` and `conn`) answers `invalid_node`, and so does one whose module cannot give its schemas for those settings. A type this organisation may not use (no app defines it, or its app pack is off or not released to this organisation) answers `unknown_module_type`. Either answers for that node alone: the rest of the batch is answered. `conn` is handed to the module as given and never read, so no connection or credential is in the answer. Needs membership only; nothing is written.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "nodes"
                ],
                "properties": {
                  "nodes": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 300,
                    "items": {
                      "type": "object",
                      "required": [
                        "id",
                        "type"
                      ],
                      "additionalProperties": false,
                      "properties": {
                        "id": {
                          "type": "integer",
                          "minimum": 1,
                          "description": "The node's id in its flow; the key of its answer."
                        },
                        "type": {
                          "type": "string",
                          "maxLength": 256,
                          "description": "`<app>:<Module>`."
                        },
                        "config": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "The node's settings, as in its flow's graph. Default: none."
                        },
                        "map": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "The node's mapped values, as in its flow's graph (some modules name their outputs by them). Default: none."
                        },
                        "conn": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 256,
                          "description": "The node's connection id, as in its flow's graph. Never read."
                        }
                      }
                    }
                  },
                  "forNewModules": {
                    "type": "boolean",
                    "description": "Set when the nodes are modules being added to a flow: each setting's `ui.default` then also follows the organization's settings where the module says so (today: a new **Create a document and wait** step's reminders follow **Auto-Reminder (days)**). Leave it out to read the forms of modules already in a flow."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Each node's schemas, or why it has none, by node id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "By node id, as text.",
                      "additionalProperties": {
                        "oneOf": [
                          {
                            "$ref": "#/components/schemas/FlowNodeSchemas"
                          },
                          {
                            "$ref": "#/components/schemas/FlowNodeSchemaError"
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON; `nodes` missing, empty or longer than 300; a node that is not an object; a node id that is not a positive integer; or an id given twice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "description": "The request body is too large (`graph_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/flows/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getFlow",
        "tags": [
          "Flows"
        ],
        "summary": "Get a flow with its draft",
        "description": "The flow, its draft graph, and the draft's validation: `issues` (each with a stable `code`), the `start` module id, and the ids of modules no path reaches (`unconnected`), and, only when there are some, `warnings`: what a module found in its own settings that stops no run, such as a credential typed into an HTTP request's header (code `credential_in_headers`), or a module that uses its connection with none chosen (code `connection_not_chosen`, on `conn`; an HTTP module set to No authentication uses none). Only `connection_not_chosen` stops anything: an active flow is not saved, and a flow is not turned on, with it (it is then one of the `issues`). A draft may hold issues; an active flow's saved version may not. Warnings never stop a save, an activation or a run. `published_version_no` is the number of the saved version the flow runs, as the list shows it; null before its first save.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The flow.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true,
                      "properties": {
                        "published_version_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "published_version_no": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "The number of the saved version the flow runs; null before its first save."
                        },
                        "next_version_no": {
                          "type": "integer",
                          "minimum": 1,
                          "description": "The number the flow's next save will take."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateFlow",
        "tags": [
          "Flows"
        ],
        "summary": "Rename a flow or move it to another folder",
        "description": "Needs `flows.edit`. A flow in the trash answers 409 `flow_trashed`. A flow whose name or folder another request changed while this one was being handled answers 409 `flow_changed` and is left as that request made it: reload it and try again. A new name and a new folder are each recorded in the audit log with the old and the new value, together with the change; a value that stays the same is not.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "folder": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "description": "Null or blank removes the folder."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The flow.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "description": "The request body is too large (`graph_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteFlow",
        "tags": [
          "Flows"
        ],
        "summary": "Delete a flow for good",
        "description": "Only a flow already in the trash can be deleted; any other answers 409 `flow_not_trashed`. Its versions are deleted with it. Needs `flows.edit`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/{id}/draft": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "put": {
        "operationId": "saveFlowDraft",
        "tags": [
          "Flows"
        ],
        "summary": "Autosave the draft",
        "description": "Replaces the draft graph. Send the `draft_rev` you loaded as `base_rev`: if the draft changed since, the answer is 409 `draft_conflict` and nothing is written. A draft may hold validation issues. Needs `flows.edit`. A note on a module or a link may carry `by` (the user id of who wrote it) and `at` (when, in UTC, as `2026-10-16T09:30:00Z`), set by the builder: a display aid, not an audit record. A `by` that names no member of this organisation is dropped (one unchanged from the saved draft stays, so a former member keeps their byline), and the note is kept.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "graph"
                ],
                "properties": {
                  "graph": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "base_rev": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "draft_rev": {
                          "type": "integer"
                        },
                        "draft_updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "validation": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, a bad `base_rev`, or a graph that does not parse (`invalid_graph`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "description": "The graph or the request body is too large (`graph_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/flows/{id}/save": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "saveFlowVersion",
        "tags": [
          "Flows"
        ],
        "summary": "Save the draft as a new version",
        "description": "Copies the draft into a new numbered version and makes it the published one. An active flow whose draft has issues answers 409 `invalid_graph` with the `issues`; for an active flow a module's connection must be chosen where the module uses one (issue `connection_not_chosen`), and be one of the organization's, of the module's app, and usable now (issues `connection_not_found`, `connection_wrong_app`, `connection_unusable`), a data structure a module's setting names (such as a JSON module's) must be one of the organization's (issue `data_structure_not_found`), each *Call a flow* module must call one of the organization's flows that is on with the On demand schedule (issues `call_target_not_found`, `call_target_not_on_demand`; another organization's flow is one that does not exist), and no flow may call itself, directly or through other flows' saved versions (`call_cycle`), and its start must fit the schedule it runs on: Immediately needs a flow that starts with an instant trigger (`needs_instant_trigger`), and a flow that starts with Watch records (instant) runs Immediately only (`instant_trigger_needs_immediately`); one that starts with Custom webhook needs a schedule of its own, not the default every 15 minutes (`instant_trigger_needs_schedule`); a flow that starts with Start flow runs On demand only (`on_demand_trigger_needs_on_demand`). A draft that would let the flow call itself, directly or through other flows (a *Call a flow* module that calls it, or calls a flow whose saved version leads back to it), is never saved, whether the flow is on or off: 409 `call_cycle`, whose `error` names the flow the module calls (`We can’t save your flow because calling '<name>' will create an infinite loop in your flow chain. Update or delete the related Call a flow module to continue.`) and whose `issues` name the modules. A confidential flow is saved with incomplete runs off (`settings.dlq` false), so Retry (Break) is an issue in it (`break_needs_incomplete_runs`). An optional `base_rev` guards against saving a draft that changed since it was loaded (409 `draft_conflict`). A flow turned on or off, or rescheduled, between these checks and the save answers 409 `version_changed`, and nothing is saved: reload the flow and try again. A draft that is the live version's graph is not saved again: the answer is the live version with `changed` false, and no version and no audit entry are written (a second press of Publish, or another window that published the same draft). A module that leaves a required field empty is refused, whether the flow is on or off: 422 `required_missing`, whose `issues` name each field by module (`moduleId`) and path (`field`); nothing is saved. The draft itself is kept, and an autosave (`PUT /api/v1/flows/{id}/draft`) is never refused for it. A flow that is on and has problems answers its 409 first; a schedule misfit, `version_changed` and `changed` false come after. Needs `flows.edit`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "base_rev": {
                    "type": "integer"
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "description": "What changed, kept with the version and in the audit log: one line of plain text, at most 200 characters (counted as Unicode code points). Blank or left out: no note. A line break or another control character, or a longer note, answers 400 `invalid_note`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new version and the validation of what was saved; or, when the draft is the live version's graph, the live version, with `changed` false.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "version": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string"
                            },
                            "version_no": {
                              "type": "integer"
                            },
                            "created_at": {
                              "type": "string",
                              "format": "date-time"
                            }
                          }
                        },
                        "validation": {
                          "type": "object",
                          "additionalProperties": true
                        },
                        "changed": {
                          "type": "boolean",
                          "description": "True when this save made a new version and made it live; false when the draft was the live version already, so nothing was written."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "description": "The request body is too large (`graph_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/RequiredMissing"
          }
        }
      }
    },
    "/api/v1/flows/{id}/versions": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listFlowVersions",
        "tags": [
          "Flows"
        ],
        "summary": "List a flow's saved versions",
        "description": "Newest first. Versions are kept 60 days; the published version is kept as long as the flow.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The versions, without their graphs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "version_no": {
                            "type": "integer"
                          },
                          "issues_count": {
                            "type": "integer"
                          },
                          "is_published": {
                            "type": "boolean"
                          },
                          "created_by": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_by_name": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The saver's name, else their email, while they are a member of the organization; null otherwise."
                          },
                          "note": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The version's note; null for a version saved without one."
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/{id}/versions/{versionId}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "versionId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getFlowVersion",
        "tags": [
          "Flows"
        ],
        "summary": "Get one saved version with its graph",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The version.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/{id}/versions/{versionId}/restore": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "versionId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "restoreFlowVersion",
        "tags": [
          "Flows"
        ],
        "summary": "Load a saved version into the draft",
        "description": "Replaces the draft with the version's graph. The published version does not change until the next save. Needs `flows.edit`; recorded in the audit log (which version), together with the change.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "The draft now holds the version.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "draft_rev": {
                          "type": "integer"
                        },
                        "draft_updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "restored_version_no": {
                          "type": "integer"
                        },
                        "validation": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/{id}/versions/{versionId}/publish": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "versionId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "publishFlowVersion",
        "tags": [
          "Flows"
        ],
        "summary": "Make a saved version live",
        "description": "Makes a saved version the published one: the version the flow runs when it is on, from its next run. The draft is not changed. The version must be one the history still keeps (60 days, or the published one). On a flow that is on, the version must pass the checks turning the flow on makes, or the answer is 409 with the same codes (`invalid_graph` with `issues`, `invalid_schedule`, `needs_instant_trigger`, `instant_trigger_needs_immediately`, `instant_trigger_needs_schedule`, `on_demand_trigger_needs_on_demand`). The version already live answers `changed: false` and changes nothing. With `expect_version_id`, the call answers 409 `version_changed` when another version is live now. It also answers 400 `invalid_via` (a `via` other than `undo` or `make_live`) or `invalid_expect_version` (an `expect_version_id` that is not an id or null), 409 `flow_trashed` for a flow in the trash, and 409 `version_changed`, changing nothing, when the flow is turned on or off, or rescheduled, during the call. Recorded in the audit log (`version_published`). Needs `flows.edit`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "via": {
                    "type": "string",
                    "enum": [
                      "undo",
                      "make_live"
                    ],
                    "default": "make_live",
                    "description": "Why, as the audit log records it: undoing a publish, or making an older version live."
                  },
                  "expect_version_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 64,
                    "description": "The version you saw live (null: none). Another version live now answers 409 `version_changed`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The version is live.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "published_version_id": {
                          "type": "string"
                        },
                        "published_version_no": {
                          "type": "integer"
                        },
                        "previous_version_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "previous_version_no": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "is_active": {
                          "type": "boolean"
                        },
                        "changed": {
                          "type": "boolean",
                          "description": "False when the version was live already: nothing was changed or recorded."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/{id}/trash": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "trashFlow",
        "tags": [
          "Flows"
        ],
        "summary": "Move a flow to the trash",
        "description": "Turns the flow off and keeps it restorable for 30 days (`keep_until`). Needs `flows.edit`; an active flow needs `flows.activate` too, since moving it to the trash turns it off (a 403 names `required_permission`). A flow read as off that was turned on before the move answers 409 `flow_changed` and stays as it is. A flow already in the trash answers 409 `flow_trashed`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "Trashed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "trashed_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "keep_until": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/{id}/restore": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "restoreFlow",
        "tags": [
          "Flows"
        ],
        "summary": "Take a flow out of the trash",
        "description": "The flow comes back turned off. Its trigger, when it polls, must be told where to start again: what was chosen and where it stood are dropped, so its next check starts From now on until `PUT /flows/{id}/trigger/start` chooses otherwise; records it already processed are never started again. A flow that is not in the trash answers 409 `flow_not_trashed`. Needs `flows.edit`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "The flow.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/{id}/runs": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listFlowRuns",
        "tags": [
          "Flows"
        ],
        "summary": "List a flow's runs",
        "description": "Newest first. Runs are kept for a year; their inputs and outputs for 30 days (`payloads_purged`).",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 200; default 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "name": "retries",
            "in": "query",
            "required": false,
            "description": "Retries of incomplete runs (`trigger_kind` `retry`): `include` (the default) lists them with the other runs, `exclude` leaves them out (as Make lists a scenario's executions), `only` lists them alone.",
            "schema": {
              "type": "string",
              "enum": [
                "include",
                "exclude",
                "only"
              ],
              "default": "include"
            }
          },
          {
            "name": "check_runs",
            "in": "query",
            "required": false,
            "description": "Check runs (`check_run`: checks of a trigger that polls which found nothing new): `include` (the default) lists them, `exclude` hides them (Make's Hide check runs). Combines with `retries`.",
            "schema": {
              "type": "string",
              "enum": [
                "include",
                "exclude"
              ],
              "default": "include"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only runs with this status.",
            "schema": {
              "type": "string",
              "enum": [
                "running",
                "waiting",
                "success",
                "warning",
                "error",
                "stopped"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The runs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FlowRunListItem"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "startFlowRun",
        "tags": [
          "Flows"
        ],
        "summary": "Run the draft once",
        "description": "Runs the flow's current draft once, as Make's Run once does. `real` runs every module for real and needs `flows.activate`; `safe` runs every module that writes through its simulation, sends nothing, and needs `flows.edit`. A draft with problems is refused (`invalid_graph`, with `issues`); in either mode a module's connection must be one of the organization's, of the module's app, and usable now (issues `connection_not_found`, `connection_wrong_app`, `connection_unusable`), and a data structure a module's setting names (such as a JSON module's) must be one of the organization's (issue `data_structure_not_found`), and each *Call a flow* module must call one of the organization's flows that is on with the On demand schedule (issues `call_target_not_found`, `call_target_not_on_demand`; another organization's flow is one that does not exist), and no flow may call itself, directly or through other flows' saved versions (`call_cycle`). The run starts in the background: read it with `GET /flows/{id}/runs/{runId}`. A real run is recorded in the audit log. With `existing_data`, the run reuses the start module output of the flow's latest run that has one, check runs passed over (Make's Run with existing data): nothing is fetched again, every later module runs again, and the run is logged as a replay of that run (`replay_of`). It needs `flows.activate` in either mode; with no earlier run to reuse it answers 409 (`no_existing_data`). With `existing_run_id` beside `existing_data` (Make's list of earlier runs under the down arrow next to Run once), the run reuses that run's start output and inputs instead of the latest run's. It needs the same `flows.activate`, in either mode, checked before the run is looked up. The run must be one of this flow's runs in the organization: another organization's run, another flow's run or one that does not exist answers 404 (`Run not found`). A run that cannot be run again with its data answers 409 (`not_reusable`) with its `reason`, and nothing starts: `check_run` (a check of a polling trigger that found nothing new: it started nothing), `single_module` (a run of one module alone, Run this module only), `confidential` (the run kept no data), `purged` (its data is past the 30 days it is kept, or gone) or `no_trigger_output` (it logged no real output for the draft's start module; for a draft that starts with *Start flow*, it was given no inputs). `existing_run_id` without `existing_data` `true`, or that is not text of 1 to 100 characters, is refused with 400 (`invalid_existing_run_id`); beside `only_module` it is refused as `invalid_only_module`, beside `start_record_id` as `invalid_start_record`. A real run records the chosen run's id in the audit log (`existing_run_id`), never its data. A draft whose trigger polls (a Watch module) is checked first, as the schedule checks it: what the check finds starts the run with those records as the trigger's output; a real run claims them and the trigger's position, committed when the run first sends a document or ends with Success or Warning and given back otherwise, while a safe test claims nothing and never moves the trigger. A trigger with no position yet (never checked, restored from the trash, its settings changed, or a new choice) reads from where it was chosen to start (`PUT /flows/{id}/trigger/start`), From now on when nothing was chosen. A check that finds nothing new starts no run: it is recorded as a check run, answered with `status` `success` and `check_run`; a check that fails is recorded as a run ended with `error`. While another run holds the trigger's records, a real run is refused with 409 (`trigger_busy`). A real run of a draft whose trigger settings differ from the saved version's is refused with 409 (`trigger_settings_changed`) when the trigger's position or start was made for other settings: they are the saved trigger's, which the schedule reads next, so save the flow first. Once saved, the run resets them as the schedule's next check would. A safe test of a flow that sends documents (SignFlow's *Create a document and wait*) sends none and waits for none: `document_outcomes` gives, for each document module, the outcome the tester chose (`completed` takes the module's signed path; `declined`, `voided` and `expired` its declined path) and, optionally, the field values its signers filled in, and the module outputs its simulation of that outcome. Every bundle the module is given takes the same outcome. Every document module of the draft needs one (`document_outcomes_required`, naming the `modules`); naming any other module, a value that is not one of these, or `document_outcomes` in a real run is refused (`invalid_document_outcomes`). The choices are kept with the run as data: for 30 days, and not past its end for a confidential flow. With flow inputs (the draft's interface, or a draft that starts with *Start flow*), `inputs` gives them by input name: each value, or the input's default when it is not given, is checked against the input's type and coerced to it as a module's parameters are (text `\"5\"` arrives as the number 5). Bad inputs are refused before any run starts with 400 (`invalid_inputs`, `type` `InvalidConfigurationError`), whose `error` is Make's message, `Bad Request. Validation failed for <count> parameter(s).` followed by each problem in the interface's order, then the keys it does not have: `Invalid number in parameter 'n'.`, `Missing value of required parameter 'req'.` (an empty text counts as missing), `Unexpected parameter 'extra' found.` (a flow with no inputs takes none); for example `Bad Request. Validation failed for 1 parameter(s). Invalid number in parameter 'n'.`. `problems` lists the problems alone. The run keeps its inputs as checked, as data (for 30 days, and not past its end for a confidential flow), and reads them as `var.input.<name>`; a flow that starts with *Start flow* also gets them as its start module's bundle. With `existing_data` the run reuses the inputs the earlier run was given, and `inputs` is refused; a flow that starts with *Start flow* logs no start output (it costs no operation), so its latest run given inputs is reused. A draft that starts with *Custom webhook* runs with the requests waiting in the flow's webhook queue: up to the module's `maxResults` of them (1 when it sets none), the oldest first, each one bundle of the trigger, which counts one operation. A real run claims them, consumed when it first sends a document or ends with Success or Warning and put back at the head of the queue otherwise; a safe test claims none. With no request waiting it answers 409 (`queue_empty`) and starts nothing. Only a flow that is on with On demand or a timed schedule keeps its requests for such a run: in either mode a flow that is off answers 409 (`not_active`: it keeps no request while off), and one on Immediately 409 (`runs_immediately`: each request runs as it comes). A real run of a flow that processes data in order (`settings.sequential`, of the draft or of the saved version) is refused with 409 (`trigger_busy`), claiming nothing, while a run of the flow is going, it has an incomplete run pending or retrying, or one of its requests is claimed by another run.\n\nRun this module only (Make's *Run this module only*): with `only_module`, one module of the draft runs alone, so you can see what it outputs. Nothing before or after it runs: no error route, no aggregation, and no incomplete run is kept. `input` gives values by field name in place of the module's mapped fields of those names, checked and coerced as mapped values are; the module's other fields are mapped as in the flow, with no earlier module's output to read (a value mapped from one is empty). Without `input` the module runs with its own mapping. The permissions are Run once's: `real` runs the module for real and needs `flows.activate`, `safe` simulates it when it writes and needs `flows.edit`; with `input` also `flows.edit` in either mode (the input is a new mapping for the module: it changes what the module does, and where it sends its data and its connection's credentials). An API token likewise needs the `flows:run` scope, and with `input` also `flows:write`. The whole draft is checked as for Run once (`invalid_graph`), and the flow's `inputs` are checked as Run once checks them. In a safe test, a module that sends documents needs its outcome in `document_outcomes` (`document_outcomes_required`), and no other module's. Refused with 400 before anything starts: an `only_module` that is not the id of one of the draft's modules, or with `existing_data` (`invalid_only_module`); an `input` that is not an object (`invalid_input`), or over 524288 bytes as JSON (413 `input_too_large`); a module that cannot run alone, with its `kind` (`module_cannot_run_alone`: a trigger, which runs with Run once, a Router, an If-else, a Merge, an aggregator or an error handler); a module the flow's start does not reach (`module_not_connected`). The run is listed with `trigger_kind` `module` and is never replayed nor reused by `existing_data`. A real one is recorded in the audit log with the module's id and whether an input was given, never the input. The input is kept with the run as data: for 30 days, and not past its end for a confidential flow.\n\nA safe test from a chosen record (a named deviation from Make, for reliability: Make has none, and its *Choose where to start* moves the trigger): with `start_record_id` and `mode` `safe`, the draft runs from one record its polling trigger lists, that record as the trigger's output, as a check would hand it. The record is read through the trigger's list of records to start after (the list `GET /flows/{id}/nodes/{nodeId}/start-records` answers, with the start module's draft settings and its connection): it must be one the trigger lists now, among the first 100 it can show, and the trigger must give the record's data with it. No check runs: nothing is claimed or marked as seen, and the trigger's position, its start choice and what it has seen stay exactly as they were, so the schedule's next check reads from where it stood; a run that holds the trigger's records does not stop it. The permission and checks are a safe Run once's: `flows.edit`, the whole draft (`invalid_graph`), every document module's outcome (`document_outcomes_required`), the flow's `inputs`. An API token needs `flows:run` and, as that list of records does (it reads the connected system with the organization's connection), `flows:write`. It is not recorded in the audit log, as no safe test is. Refused before anything starts: a `start_record_id` that is not text of 1 to 1000 characters, a real run (the default mode included), or with `existing_data` or `only_module` (400 `invalid_start_record`); a draft whose start is not a trigger that polls, lists its records and offers *Choose manually* with its settings, or a listed record the trigger gives no data for (422 `start_record_unavailable`); a record the trigger does not list now (422 `start_record_not_listed`); a list that fails (502 `start_records_failed`, with the engine's error class `type`, the message redacted of the connection's credentials). The run is listed with `trigger_kind` `manual`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:run"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "real",
                      "safe"
                    ],
                    "default": "real"
                  },
                  "existing_data": {
                    "type": "boolean",
                    "default": false,
                    "description": "Reuse the start module output of the flow's latest run that kept one (not confidential, not past the 30 days its data is kept)."
                  },
                  "existing_run_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100,
                    "nullable": true,
                    "description": "With `existing_data` `true` only: the id of the flow's run whose start output and inputs to reuse, instead of the latest run's (`GET /flows/{id}/runs` lists them). Null or absent: the latest run. A check run, a run of one module alone, a confidential run, a run whose data is gone, or a run with no start output the draft can reuse is refused (409 `not_reusable`, with its `reason`); another organization's or another flow's run is a 404.",
                    "example": "run_V1StGXR8_Z5jdHi6B-myT"
                  },
                  "document_outcomes": {
                    "type": "object",
                    "description": "A safe test only: by document module id, the outcome the tester chose for that module's documents. Required for a safe test of a draft with document modules, one entry for each of them and no other.",
                    "propertyNames": {
                      "pattern": "^[1-9][0-9]*$"
                    },
                    "additionalProperties": {
                      "type": "object",
                      "required": [
                        "outcome"
                      ],
                      "additionalProperties": false,
                      "properties": {
                        "outcome": {
                          "type": "string",
                          "enum": [
                            "completed",
                            "declined",
                            "voided",
                            "expired"
                          ],
                          "description": "`completed`: the document was signed, and the module's signed path runs. `declined`, `voided` or `expired`: its declined path runs, with that outcome."
                        },
                        "fields": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string"
                          },
                          "default": {},
                          "description": "The values the signers filled in, as text by field key: the signed bundle's `fields`, over the values the document was prefilled with. Read only for `completed`."
                        }
                      }
                    },
                    "example": {
                      "2": {
                        "outcome": "completed",
                        "fields": {
                          "amount": "100"
                        }
                      },
                      "5": {
                        "outcome": "declined"
                      }
                    }
                  },
                  "inputs": {
                    "type": "object",
                    "description": "The flow's inputs by input name, as its interface defines them. Not with `existing_data`.",
                    "additionalProperties": true,
                    "example": {
                      "email": "dana@example.com",
                      "amount": "100"
                    }
                  },
                  "only_module": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Run this module only: the id of the draft's module to run alone."
                  },
                  "input": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "With `only_module`: values by field name, in place of the module's mapped fields of those names. Read only with `only_module`. A new mapping for the module, so it also needs `flows.edit` and, for an API token, the `flows:write` scope.",
                    "example": {
                      "recordId": "rec_1"
                    }
                  },
                  "start_record_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1000,
                    "description": "A safe test from a chosen record (`mode` `safe` only): the `record_id` of one of the records the draft's polling trigger lists (`GET /flows/{id}/nodes/{nodeId}/start-records`). The run starts from that record alone and never moves the trigger. Not with `existing_data` or `only_module`. It reads that list with the organization's connection, as the list itself does, so an API token also needs the `flows:write` scope.",
                    "example": "ORDNAME='SO1'"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The run started; or, for a draft whose trigger polls, its check was recorded (`check_run`, or a run ended with `error`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "running",
                            "success",
                            "error"
                          ],
                          "description": "`running` for a run that started. A draft whose trigger polls: `success` for a check that found nothing new (a check run), `error` for a check that failed."
                        },
                        "mode": {
                          "type": "string",
                          "enum": [
                            "real",
                            "safe"
                          ]
                        },
                        "draft_rev": {
                          "type": "integer"
                        },
                        "started_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "replay_of": {
                          "type": "string",
                          "description": "With `existing_data`: the run whose start output this run reuses."
                        },
                        "check_run": {
                          "type": "boolean",
                          "description": "Set for a check that found nothing new: the run is a check run, ended with Success, and no run started."
                        },
                        "only_module": {
                          "type": "integer",
                          "description": "With `only_module`: the module the run runs alone."
                        },
                        "start_record_id": {
                          "type": "string",
                          "description": "With `start_record_id`: the record the safe test starts from."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A malformed request; a draft with problems (`invalid_graph`, with its `issues`); inputs that are not valid (`invalid_inputs`, `type` `InvalidConfigurationError`, with its `problems`); document outcomes that are not valid (`invalid_document_outcomes`); or a safe test with a document module whose outcome was not chosen (`document_outcomes_required`, with those `modules`); with `only_module`: a module id that is not one of the draft's, or `existing_data` (`invalid_only_module`), an `input` that is not an object (`invalid_input`), a module that cannot run alone (`module_cannot_run_alone`, with `module_id` and `kind`) or one the start does not reach (`module_not_connected`, with `module_id`); with `start_record_id`: a record id that is not one, a real run, or `existing_data`, `only_module` or `existing_run_id` beside it (`invalid_start_record`); an `existing_run_id` that is not text of 1 to 100 characters, or given without `existing_data` `true` (`invalid_existing_run_id`), or beside `only_module` (`invalid_only_module`).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "modules": {
                          "type": "array",
                          "items": {
                            "type": "integer"
                          },
                          "description": "`document_outcomes_required`: the document modules with no outcome chosen, by id."
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "InvalidConfigurationError"
                          ],
                          "description": "`invalid_inputs` for values the inputs refused: Make's class for them."
                        },
                        "problems": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "`invalid_inputs` for values the inputs refused: Make's message for each problem, in the interface's order, then the keys it does not have."
                        },
                        "module_id": {
                          "type": "integer",
                          "description": "`module_cannot_run_alone`, `module_not_connected`: the module."
                        },
                        "kind": {
                          "type": "string",
                          "description": "`module_cannot_run_alone`: the module's kind (`trigger`, `router`, `ifelse`, `merge`, `aggregator` or `directive`)."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The flow or its trigger is in a state that forbids the run (for example `flow_trashed`, `trigger_busy`, `trigger_settings_changed`, `queue_empty`, `not_active`, `runs_immediately`); with `existing_data`, no earlier run has data to reuse (`no_existing_data`); with `existing_run_id`, the chosen run cannot be run again with its data (`not_reusable`, with its `reason`). Nothing starts.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "reason": {
                          "type": "string",
                          "enum": [
                            "check_run",
                            "single_module",
                            "confidential",
                            "purged",
                            "no_trigger_output"
                          ],
                          "description": "`not_reusable`: why the chosen run cannot be run again with its data."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "413": {
            "description": "With `only_module`: an `input` over 524288 bytes as JSON (`input_too_large`), refused before anything is read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "With `start_record_id`: the draft's start cannot run a test from a record it lists (`start_record_unavailable`: it is not a trigger that polls and lists its records with *Choose manually* offered by its settings, or the trigger gives no data for the record), or the trigger does not list that record now (`start_record_not_listed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "With `start_record_id`: the trigger could not list its records (`start_records_failed`): its connection cannot be used, or the connected system failed or refused the read. The engine's error class is `type`; the message is redacted of the connection's credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlowStartRecordsRefused"
                }
              }
            }
          },
          "503": {
            "description": "Runs are not available here (`runs_unavailable`), or the run could not be started (`run_not_started`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/flows/{id}/runs/replay": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "replayFlowRuns",
        "tags": [
          "Flows"
        ],
        "summary": "Replay runs",
        "description": "Runs the flow's current saved version again for each run, with the start module output that run logged and the flow inputs it was given (Make's Replay): nothing is fetched again, every later module runs again, and each new run is logged as a replay of its run (`replay_of`). Up to 100 runs a call; a run listed twice is replayed once. All or nothing: when any run has nothing to replay, the answer is 409 (`not_replayable`) with each such run and its reason — `not_found`, `check_run` (a check of a polling trigger that found nothing new: it started nothing to replay), `single_module` (a run of one module alone, Run this module only: it ran no flow to run again), `confidential` (the run kept no data), `purged` (its data is past the 30 days it is kept) or `no_trigger_output` (it logged no real output for the start module the saved version has; for a version that starts with *Start flow*, which logs none, it was given no inputs) — and nothing starts. The flow must have been saved (`not_published`) with no problems (`invalid_graph`, with `issues`): a module's connection must be one of the organization's, of the module's app, and usable now (issues `connection_not_found`, `connection_wrong_app`, `connection_unusable`), and a data structure a module's setting names (such as a JSON module's) must be one of the organization's (issue `data_structure_not_found`), and each *Call a flow* module must call one of the organization's flows that is on with the On demand schedule (issues `call_target_not_found`, `call_target_not_on_demand`; another organization's flow is one that does not exist), and no flow may call itself, directly or through other flows' saved versions (`call_cycle`). Needs `flows.activate`; recorded in the audit log.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:run"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "run_ids"
                ],
                "properties": {
                  "run_ids": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "type": "string"
                    },
                    "description": "The runs to replay."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The new runs, one per run asked for, in the same order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The new run; null when its start output was removed before it could start."
                          },
                          "replay_of": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "running",
                              "error"
                            ],
                            "description": "`error`: the run could not be started."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "503": {
            "description": "Runs are not available here (`runs_unavailable`), or the run could not be started (`run_not_started`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/flows/{id}/runs/{runId}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getFlowRun",
        "tags": [
          "Flows"
        ],
        "summary": "Read a run",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The run, with the fields the run list shows beside it (run_no, active_ms, items, waiting_documents, saved_to_retry, flow_name, started_by_name). The same membership and scope as the list; nothing the list does not show.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowRunListItem"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/{id}/runs/{runId}/operations": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listFlowRunOperations",
        "tags": [
          "Flows"
        ],
        "summary": "List a run's operations",
        "description": "One operation per module run per bundle, in the order the run reached them, without their inputs and outputs. A document still out for signature has no output yet (`has_output` false); `has_provisional_output` says its output so far can be read.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 1000; default 500.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 500
            }
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "The operations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FlowRunOperation"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/{id}/runs/{runId}/modules": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listFlowRunModuleCounts",
        "tags": [
          "Flows"
        ],
        "summary": "Count a run's operations per module",
        "description": "The run's operations, as the operations list lists them, counted per module, in module id order: what the builder shows on each module after a run, without paging through the list. Counts only, no inputs or outputs. A module with no operation in the run (one that did not run, or one that never counts as an operation, such as a router) is not listed; a run with no operations answers an empty list.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The counts, one item per module that has operations in the run.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "module_id",
                          "operations",
                          "errors",
                          "warnings",
                          "simulated"
                        ],
                        "properties": {
                          "module_id": {
                            "type": "integer"
                          },
                          "operations": {
                            "type": "integer",
                            "description": "The module's operations in the run: one per module run per bundle."
                          },
                          "errors": {
                            "type": "integer",
                            "description": "Of them, the ones whose status is `error`."
                          },
                          "warnings": {
                            "type": "integer",
                            "description": "Of them, the ones that kept warnings (a non-empty `warnings` in the operations list); not the number of warnings."
                          },
                          "simulated": {
                            "type": "integer",
                            "description": "Of them, the ones a safe test simulated."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/{id}/runs/{runId}/payload": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getFlowRunPayload",
        "tags": [
          "Flows"
        ],
        "summary": "Read an operation's input and output",
        "description": "Values are JSON; a date is `{\"$sf\":\"date\",\"v\":\"<ISO 8601>\"}` and a buffer `{\"$sf\":\"bytes\",\"v\":\"<base64>\"}`. Secrets are redacted. A confidential run, or one whose inputs and outputs were purged after 30 days, answers null for both. Needs the `flows.edit` permission, as a data store's records do: run data can hold personal information, so reading the flow is not enough. Without it the answer is 403 (`required_permission` `flows.edit`) for any step, confidential and purged runs included; another organization's flow, or a run the flow does not have, is still a 404 first. An API token still needs the `flows:read` scope.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "step",
            "in": "query",
            "required": true,
            "description": "The operation, as `step` in the operations list.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The input and output.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "step": {
                          "type": "string"
                        },
                        "module_id": {
                          "type": "integer"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "success",
                            "error"
                          ]
                        },
                        "confidential": {
                          "type": "boolean"
                        },
                        "purged": {
                          "type": "boolean"
                        },
                        "input": {},
                        "output": {
                          "description": "The operation's output. For a document still out for signature (`has_provisional_output` in the operations list), its output so far: the document, when it was sent and expires, and each signer's name, email, role and status as of when it was sent. It never holds a signing link."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/{id}/runs/{runId}/stop": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "stopFlowRun",
        "tags": [
          "Flows"
        ],
        "summary": "Stop a run",
        "description": "Asks the run to stop. The module running now finishes; nothing after it runs, and the run ends Stopped. A run waiting for documents it sent is woken to stop: each document it still waits for is sent a stop event, and the run ends Stopped without waiting for a signature; a stop event that does not arrive is sent again by the platform until the wait is closed. Needs the permission that starts a run of its mode. Asking again records nothing new and sends the stop events again. A run that has ended answers 409 (`run_ended`).",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:run"
        ],
        "responses": {
          "200": {
            "description": "The run, with its stop request.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowRun"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/{id}/incomplete-runs": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listFlowIncompleteRuns",
        "tags": [
          "Flows"
        ],
        "summary": "List a flow's incomplete runs",
        "description": "Newest first. With *Store incomplete executions* on, a failure nothing handled (or a Break module) keeps its bundle as an incomplete run, which can be retried automatically, by a person, or deleted. A resolved one stays listed; a retry that fails again updates its incomplete run rather than adding one, and while a retry is going, what it keeps is not listed apart from the incomplete run it retries (shown `retrying`). Reading needs membership only.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "state",
            "in": "query",
            "required": false,
            "description": "Only those in this state.",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "retrying",
                "unresolved",
                "resolved"
              ],
              "description": "`pending`: never retried, or waiting for its next automatic retry (`next_retry_at`); a `pending` one with no `next_retry_at` has no automatic retry (its error has none, or its retry could not be prepared) and waits for a person, who retries or deletes it. `retrying`: a retry is going. `unresolved`: retried and failed, with no automatic retry left; a person retries or deletes it. `resolved`: a retry finished with it."
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 200; default 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "The incomplete runs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FlowIncompleteRun"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/{id}/incomplete-runs/retry": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "retryFlowIncompleteRuns",
        "tags": [
          "Flows"
        ],
        "summary": "Retry incomplete runs",
        "description": "Retries up to 100 incomplete runs of the flow, each from where it was kept, on the flow it ran, each as a run of its own. All or nothing: every item is checked first, and when any cannot be retried nothing starts and the answer lists each such item with its reason — 409 (`not_retryable`) when any is refused for its state (`not_found`, `incomplete_run_retrying`, `incomplete_run_resolved`, `incomplete_run_running`), 422 (`retry_refused`) when only preparing the retry refused them (the refusal's code, with its `errors` or `issues`). An item listed twice is a 400 (`duplicate_items`); a mapping is edited one incomplete run at a time. A person's retries are not held to the 3 automatic retries a flow runs at a time, and the flow need not be on. Needs `flows.activate`; each retry is recorded in the audit log.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:run"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "items"
                ],
                "properties": {
                  "items": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "type": "object",
                      "required": [
                        "runId",
                        "step"
                      ],
                      "additionalProperties": false,
                      "properties": {
                        "runId": {
                          "type": "string"
                        },
                        "step": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The retries started, in the order of the items; one that could not be started is `error` (`id` null when its run was never written), and its incomplete run is pending again. When starting some of them fails partway, the retries already started stand and the rest are `error`, their incomplete runs pending again (`id` null, or the run's `id` when its run was written but could never start and has ended), except a retry whose run may have started: it keeps its incomplete run and is answered as its run stands, with its `id` (`running`, or `error` once that run ended), so its bundle never runs twice.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FlowIncompleteRunRetryStarted"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Some items cannot be retried now (`not_retryable`, with `items`), or the flow is in the trash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Preparing some retries was refused (`retry_refused`, with `items`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Runs are not available here (`runs_unavailable`), or the retry could not be started (`run_not_started`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/flows/{id}/incomplete-runs/{runId}/{step}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "description": "The run that kept it.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "step",
          "in": "path",
          "required": true,
          "description": "Its step, e.g. `m4:1.2:run`, URL-encoded as one path segment.",
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getFlowIncompleteRun",
        "tags": [
          "Flows"
        ],
        "summary": "Read an incomplete run",
        "description": "The incomplete run with what it kept (`snapshot`: null once its run's data was purged) and its retries, newest first. Needs the `flows.edit` permission, as an operation's input and output do: what it kept can hold personal information, so reading the flow is not enough (listing the incomplete runs still needs membership only). Without it the answer is 403 (`required_permission` `flows.edit`); another organization's flow, or an incomplete run the flow does not have, is still a 404 first. An API token still needs the `flows:read` scope.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The incomplete run.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowIncompleteRunDetail"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteFlowIncompleteRun",
        "tags": [
          "Flows"
        ],
        "summary": "Delete an incomplete run",
        "description": "Deletes the incomplete run and the data it kept, the data first: when that cannot be deleted the answer is 503 (`snapshot_not_deleted`) and the incomplete run stays, to be deleted again. Its run and the run's log stay. Not while a retry of it is going (409 `incomplete_run_retrying`) or the run that kept it is still going (409 `incomplete_run_running`). Needs `flows.activate`; recorded in the audit log.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:run"
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "503": {
            "description": "The data it kept could not be deleted (`snapshot_not_deleted`); the incomplete run stays.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/flows/{id}/incomplete-runs/{runId}/{step}/retry": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "description": "The run that kept it.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "step",
          "in": "path",
          "required": true,
          "description": "Its step, e.g. `m4:1.2:run`, URL-encoded as one path segment.",
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "retryFlowIncompleteRun",
        "tags": [
          "Flows"
        ],
        "summary": "Retry an incomplete run",
        "description": "Retries the incomplete run from where it was kept, on the flow its run ran (or on the flow an earlier retry ran with an edited mapping), as a run of its own (`trigger_kind` `retry`, `retry_of`, `retry_step`). With `map`, the module that failed runs with that mapping instead, checked as a save would check it. The flow need not be on, and a person's retry is not held to the 3 automatic retries a flow runs at a time. Needs `flows.activate`, and with `map` also `flows.edit` (a new mapping changes what the flow does); an API token likewise needs the `flows:run` scope, and with `map` also `flows:write`. Recorded in the audit log (whether the mapping was edited, never the mapping).",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:run"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "map": {
                    "type": "object",
                    "description": "A new mapping for the module that failed, as the flow graph writes a module's `map`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The retry started.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "running"
                          ]
                        },
                        "retry_of": {
                          "type": "string"
                        },
                        "retry_step": {
                          "type": "string"
                        },
                        "map_edited": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The incomplete run cannot be retried now: `incomplete_run_retrying` (a retry is going), `incomplete_run_resolved`, `incomplete_run_running` (the run that kept it is still going), or `flow_trashed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The retry cannot be prepared.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlowIncompleteRunRetryRefused"
                }
              }
            }
          },
          "503": {
            "description": "Runs are not available here (`runs_unavailable`), or the retry could not be started (`run_not_started`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/flows/{id}/waits/summary": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getFlowWaitSummary",
        "tags": [
          "Flows"
        ],
        "summary": "Count a flow's documents per document module",
        "description": "For each document module, the documents waiting for signers now (from every run of the flow) and how many of one run's documents were signed or declined (declined, voided or expired): the run you name, or the flow's newest run that is not a check run. Counts only, no names.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "run_id",
            "in": "query",
            "required": false,
            "description": "A run of this flow; another flow's run answers 404.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The counts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowWaitSummary"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/{id}/runs/{runId}/documents": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listFlowRunDocuments",
        "tags": [
          "Flows"
        ],
        "summary": "List the documents a run sent",
        "description": "Every document the run sent, in the order it sent them, 50 a page, each with its signers. Signer names and emails are personal data: this needs the permission to edit flows (`flows.edit`).",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the page before.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of documents.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FlowDocument"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/documents": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listOpenFlowDocuments",
        "tags": [
          "Flows"
        ],
        "summary": "List the documents your flows have out for signature",
        "description": "Documents your flows sent that wait for signers, soonest expiry first (those with no expiry last), 50 a page, each with its signers. The first page also carries `totals`. Flows in the trash are left out. Needs the permission to edit flows (`flows.edit`).",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "open"
              ],
              "default": "open"
            }
          },
          {
            "name": "flow_id",
            "in": "query",
            "required": false,
            "description": "One of your flows; another organization's answers 404.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of documents.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FlowDocument"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "totals": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "On the first page only.",
                      "properties": {
                        "open": {
                          "type": "integer"
                        },
                        "expire_within_7_days": {
                          "type": "integer",
                          "description": "The waiting documents that expire within the next 7 days, including any already past their expiry."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/stats": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getFlowStats",
        "tags": [
          "Flows"
        ],
        "summary": "Your flows in numbers",
        "description": "For the last `days` days: runs (safe tests and empty checks left out), the share with no error, documents sent and how many of them are signed.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "1 to 90; default 30.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 90,
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The numbers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowStats"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/flows/incomplete-runs": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listOrgFlowIncompleteRuns",
        "tags": [
          "Flows"
        ],
        "summary": "List the runs saved to retry across your flows",
        "description": "Newest first, at most 100 a page; follow `next_cursor`. `count=1` answers `{ data: { open } }`, the number of open (pending or retrying) items, narrowed by `flow_id` and `run_id`; `state`, `limit` and `cursor` are ignored. Flows in the trash are left out.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "flow_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "run_id",
            "in": "query",
            "required": false,
            "description": "The run that saved them: a run of your organization; another organization's, or an unknown one, answers 404.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "pending",
                "retrying",
                "unresolved",
                "resolved"
              ]
            }
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 100; default 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of saved runs, or with count=1 how many are open.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "oneOf": [
                        {
                          "type": "array",
                          "items": {
                            "allOf": [
                              {
                                "$ref": "#/components/schemas/FlowIncompleteRun"
                              },
                              {
                                "type": "object",
                                "required": [
                                  "flow_id",
                                  "flow_name"
                                ],
                                "properties": {
                                  "flow_id": {
                                    "type": "string"
                                  },
                                  "flow_name": {
                                    "type": "string"
                                  }
                                }
                              }
                            ]
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "open"
                          ],
                          "properties": {
                            "open": {
                              "type": "integer"
                            }
                          }
                        }
                      ]
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/runs": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listOrgFlowRuns",
        "tags": [
          "Flows"
        ],
        "summary": "List the runs of all your flows",
        "description": "Newest first, at most 100 a page; follow `next_cursor`. Empty check runs (a scheduled check that found nothing new) are hidden unless `check_runs=include`; the first page says how many the filters hide (`hidden_checks`) and how many of the schedule's checks in the period found something (`totals`). Runs of flows in the trash are left out.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "flow_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "running",
                "waiting",
                "success",
                "warning",
                "error",
                "stopped"
              ]
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Runs that started at or after this time (ISO 8601).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "Runs that started before this time (ISO 8601).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "check_runs",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "include",
                "exclude"
              ],
              "default": "exclude"
            }
          },
          {
            "name": "retries",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "include",
                "exclude",
                "only"
              ],
              "default": "include"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 100; default 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of runs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FlowRunListItem"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "hidden_checks": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "On the first page: the check runs these filters hide (0 with check_runs=include). Null on later pages."
                    },
                    "totals": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "On the first page: the schedule's checks in the period, and how many of them found something or failed (a run you can open). Null on later pages.",
                      "properties": {
                        "checks": {
                          "type": "integer"
                        },
                        "checks_with_runs": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/{id}/runs/{runId}/links": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "runId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getFlowRunLinkCounts",
        "tags": [
          "Flows"
        ],
        "summary": "Count a run's items per link",
        "description": "For each link of the flow, how many of the run's items went along it (`passed`) and how many its filter stopped (`stopped`): router routes and the fallback, If-else branches, a document's signed and declined paths, and error routes included. A link no item tried is not listed: a disabled route, a fallback skipped because a route matched, an If-else branch not taken. Busiest links first. A run that is waiting for documents answers its counts so far (`final` false). Counts only, no inputs or outputs; a confidential run's counts are answered too. `data` is null when the run has no counts: a run from before link counts were kept, a run still running that has not waited yet, a run whose flow could not start (it no longer validates), or a run that ended on a failure of the platform before it ever waited.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The run's link counts, or null when it has none.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "nullable": true,
                      "required": [
                        "final",
                        "truncated",
                        "links"
                      ],
                      "properties": {
                        "final": {
                          "type": "boolean",
                          "description": "True: the run's walk ended and these are its final counts. False: the counts as of the run's last turn to Waiting; they change while the run goes on, and a run that ended without finishing its walk (a failure on the platform, or the sweep that ends lost runs) keeps them."
                        },
                        "truncated": {
                          "type": "boolean",
                          "description": "True when the counts were over 512 KiB and only the busiest links were kept."
                        },
                        "links": {
                          "type": "array",
                          "description": "Busiest first (passed plus stopped), then by link id.",
                          "items": {
                            "type": "object",
                            "required": [
                              "link_id",
                              "passed",
                              "stopped"
                            ],
                            "properties": {
                              "link_id": {
                                "type": "string",
                                "maxLength": 64,
                                "description": "The link's id in the flow's graph."
                              },
                              "passed": {
                                "type": "integer",
                                "minimum": 0,
                                "description": "Items that went along the link."
                              },
                              "stopped": {
                                "type": "integer",
                                "minimum": 0,
                                "description": "Items the link's filter stopped."
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/starters": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listFlowStarters",
        "tags": [
          "Flows"
        ],
        "summary": "List the starter flows",
        "description": "Read-only. Ready-made flows to begin from. Each has a name and a description in English and Hebrew, the ids of the apps it uses (in module order), its path of apps as `GET /api/v1/flows` draws one, and its graph, which `POST /api/v1/flows` takes as `graph`. A starter's modules come without settings, mappings or connections: the flow it makes needs them before it can be published. Only the starters whose every app and module this organisation may use now are listed: an app pack that is not released to it hides the starters that use it. Needs membership only; nothing is written.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The starters.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "name",
                          "description",
                          "apps",
                          "path",
                          "graph"
                        ],
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "$ref": "#/components/schemas/FlowLocalized"
                          },
                          "description": {
                            "$ref": "#/components/schemas/FlowLocalized"
                          },
                          "apps": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "The ids of the apps its modules belong to, in module order."
                          },
                          "path": {
                            "$ref": "#/components/schemas/FlowPath"
                          },
                          "graph": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "A version 3 flow graph: modules, links, settings and meta."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/flows/{id}/activate": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "activateFlow",
        "tags": [
          "Flows"
        ],
        "summary": "Turn a flow on",
        "description": "Turns the flow on: from now it runs its saved version on its schedule, in its time zone (the organization's when it has none). The flow must have been saved (`not_published`), and the saved version must have no problems (`invalid_graph`, with `issues`): a module's connection must be chosen where the module uses one (issue `connection_not_chosen`; a draft's validation names it as a warning), and be one of the organization's, of the module's app, and usable now (issues `connection_not_found`, `connection_wrong_app`, `connection_unusable`), and a data structure a module's setting names (such as a JSON module's) must be one of the organization's (issue `data_structure_not_found`), and each *Call a flow* module must call one of the organization's flows that is on with the On demand schedule (issues `call_target_not_found`, `call_target_not_on_demand`; another organization's flow is one that does not exist), and no flow may call itself, directly or through other flows' saved versions (`call_cycle`). A schedule with no runs left is refused (`schedule_ended`), and so is one whose times its `restrict` periods never allow. Immediately needs a saved version that starts with an instant trigger, such as Priority's Watch records (instant) or Custom webhook (`needs_instant_trigger`): each item its webhook receives starts one run at once, at most `maximum_runs_per_minute` a minute when set, and the rest wait in its queue. A flow that starts with Watch records (instant) runs Immediately only (`instant_trigger_needs_immediately`). One that starts with Custom webhook may also run on a timed schedule or On demand: its requests wait in its queue, and each run takes up to the module's `maxResults` of them, the oldest first (a scheduled run with nothing waiting starts nothing and is not listed); at regular intervals its first run is at once. Such a flow needs a schedule of its own: with none (every 15 minutes) it is refused (`instant_trigger_needs_schedule`). A saved version that starts with Start flow runs On demand only (`on_demand_trigger_needs_on_demand`), so a flow with no schedule of its own (every 15 minutes) needs On demand set first. An instant flow is turned off by the first run that ends with an error, whose item goes back to the head of its queue. A flow that is on answers `already_active`; a flow saved again while it was being turned on answers `version_changed`. Turning a flow on clears an earlier deactivation by errors and its error count, and closes a listening window that is open (`POST /api/v1/flows/{id}/webhook/listen`). A saved version with a required field left empty (one saved before this check existed) is refused with 422 `required_missing` and the flow stays off: fill the fields, publish, then turn it on. Turning a flow off is never refused for it. Needs `flows.activate`; recorded in the audit log.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "The flow, on, with its next run.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowLifecycle"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/RequiredMissing"
          }
        }
      }
    },
    "/api/v1/flows/{id}/deactivate": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "deactivateFlow",
        "tags": [
          "Flows"
        ],
        "summary": "Turn a flow off",
        "description": "Turns the flow off: nothing starts it on its schedule until it is turned on again. A run already going finishes. A flow that is off answers `not_active`. Needs `flows.activate`; recorded in the audit log.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "The flow, off.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowLifecycle"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/{id}/schedule": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "put": {
        "operationId": "setFlowSchedule",
        "tags": [
          "Flows"
        ],
        "summary": "Set a flow's schedule",
        "description": "Sets when the flow runs, and optionally its time zone. On a flow that is on, the new schedule applies at once (a regular interval counts from now); such a flow cannot take a schedule with no runs left (`schedule_ended`, such as one whose times its `restrict` periods never allow), Immediately unless its saved version starts with an instant trigger (`needs_instant_trigger`), or anything but Immediately when it starts with Watch records (instant) (`instant_trigger_needs_immediately`), or anything but On demand when its saved version starts with Start flow (`on_demand_trigger_needs_on_demand`). A flow that is off keeps any schedule; turning it on checks it. A schedule it cannot run answers `invalid_schedule` with one reason per problem; an unknown zone `invalid_timezone`. Needs `flows.edit`, as Save does; recorded in the audit log.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "schedule"
                ],
                "properties": {
                  "schedule": {
                    "$ref": "#/components/schemas/FlowSchedule"
                  },
                  "timezone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "An IANA time zone such as `Asia/Jerusalem`; null for the organization's. Left as is when absent."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The flow with its new schedule.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowLifecycle"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/{id}/trigger/start": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getFlowTriggerStart",
        "tags": [
          "Flows"
        ],
        "summary": "Where the flow's trigger starts",
        "description": "For a flow whose saved version starts with a trigger that polls (a Watch module): the options it offers with its settings, what was chosen, and whether its next check still starts from it (`applies`). A flow never saved, or whose saved version does not poll, answers with no `options`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "Where the trigger starts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowTriggerStart"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "chooseFlowTriggerStart",
        "tags": [
          "Flows"
        ],
        "summary": "Choose where the flow's trigger starts",
        "description": "Make's Choose where to start, for the saved version's trigger that polls: where its next check reads from. That check is its first: the trigger's position is dropped, so the choice applies to it and to no later check, which goes on from where it stood. From now on notes what that check finds and starts nothing; every other option starts what it finds, at most the trigger's Maximum number of results a run. Records the flow already processed are never started again. The choice is made for the trigger's current settings: once they change, it no longer applies and the next check starts From now on; a restore from the trash drops it too. An option the trigger does not offer with its settings, or a choice of the wrong shape, answers 400 `invalid_start_choice` with the `options` it offers. A flow in the trash answers `flow_trashed`, one never saved `not_published`, one whose saved version does not start with a trigger that polls `no_polling_trigger`, and while a run holds the trigger's records `trigger_busy` (409). For a trigger whose record keys hold where it read from (Priority's Watch records by changes since its last check; GET's `processed_until` is set), Since a specific date before the last record it took answers 409 `start_before_processed` with that time as `processed_until`, and nothing changes: such a date would start records the flow already processed again. Needs `flows.edit`; recorded in the audit log (without a picked record's id).",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FlowStartChoice"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Where the trigger starts now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowTriggerStart"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/{id}/nodes/{nodeId}/start-records": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "nodeId",
          "in": "path",
          "required": true,
          "description": "The trigger's module id in the flow's draft.",
          "schema": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,8})$"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listFlowStartRecords",
        "tags": [
          "Flows"
        ],
        "summary": "Records a trigger may start after (Choose manually)",
        "description": "Make's Choose manually, for the builder: the records the draft's trigger that polls (`nodeId`) lists for a person to pick where it starts, with the node's draft settings and its connection, in the trigger's order. At most 100 records (provisional); each label is cut at 200 characters; a record that cannot be picked (a label or record id that is not text, or a record id `PUT /api/v1/flows/{id}/trigger/start` cannot keep) is left out, and a date the trigger gives is kept only as an instant with its offset, answered in UTC. The list is the connected system's text, so it is redacted of the connection's credentials, as written or as JSON text spells them: a label shows `[redacted]` in their place (redacted, then cut), and a record whose id holds one is left out (its id could not be kept redacted); the 100 count the records kept. The pick is saved with `PUT /api/v1/flows/{id}/trigger/start` (`option` `manual`, the record's `record_id`), for the saved version's trigger. A read: it writes nothing and moves no trigger, and it is not recorded in the audit log. It reads the connected system with the organization's connection, so it needs `flows.edit` and, for an API token, `flows:write`. A node the draft does not have, or whose module is not a trigger that polls, answers 404 `node_not_found`; a trigger that cannot list records, or does not offer Choose manually with its settings, 422 `manual_start_unavailable`; a flow in the trash 409 `flow_trashed`. A read that fails answers 502 `start_records_failed` with the engine's error class (`type`), never the connected system's text unredacted.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "The records, at most 100.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "records"
                      ],
                      "properties": {
                        "records": {
                          "type": "array",
                          "maxItems": 100,
                          "items": {
                            "$ref": "#/components/schemas/FlowStartRecord"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No such flow in this organization (`Flow not found`), or the draft has no trigger that polls at this node (`code` is `node_not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The flow is in the trash (`code` is `flow_trashed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The trigger cannot list records to start after, or does not offer Choose manually with its settings (`code` is `manual_start_unavailable`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The trigger could not list its records: its connection cannot be used, or the connected system failed or refused the read. The engine's error class is `type`; the message is redacted of the connection's credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlowStartRecordsRefused"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/flows/{id}/rpc": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "listFlowRpcChoices",
        "tags": [
          "Flows"
        ],
        "summary": "A dropdown's choices from an app's RPC",
        "description": "Make's dynamic options RPC, for the builder: the choices of a field whose `ui.source` is `pack.rpc`, read from the connected system with the connection chosen for the module. `rpc` is the reference the field's source names, exactly as written; only a reference the module itself declares runs, and only for a module of an app the organization may see. `parameters` are the values of the fields the source's `params` name (text of at most 2,000 characters, a number or true or false; at most 50), each by its own name. The read runs for at most 40 seconds. At most 1,000 choices are answered, in the app's order (`truncated` counts the rest); each label is cut at 200 characters. The choices are the connected system's text, so they are redacted of the connection's credentials, as written or as JSON text spells them: a label shows `[redacted]` in their place, and a choice whose value holds one is left out. A read: it writes nothing, is not recorded in the audit log, and is answered with `Cache-Control: no-store`. It reads the connected system with the organization's connection, so it needs `flows.edit` and, for an API token, `flows:write`; at most 120 asks a minute for each API token or user. A body that is not the bounded fields, or that asks for a `use` the route does not answer, answers 400 `rpc_invalid`; a module the organization does not see, or that does not declare the reference, 404 `rpc_not_found`; a flow in the trash 409 `flow_trashed`. A read that fails answers 502 `rpc_failed` with the engine's error class (`type`), never the connected system's text unredacted; a connection of another organization reads as not found.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "x-rate-limit": {
          "requests": 120,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type",
                  "conn",
                  "rpc"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "The module's type, e.g. `slack:CreateMessage`."
                  },
                  "conn": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128,
                    "description": "The id of the connection chosen for the module."
                  },
                  "rpc": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000,
                    "description": "The reference the field's source names (`FlowSourceRef.rpc`), exactly as written."
                  },
                  "use": {
                    "type": "string",
                    "enum": [
                      "choices"
                    ],
                    "default": "choices",
                    "description": "What the reference runs for: a list's choices, the only use here (fields and outputs are asked at `POST /api/v1/flows/{id}/rpc-fields`)."
                  },
                  "parameters": {
                    "type": "object",
                    "maxProperties": 50,
                    "description": "The values of the fields the source's `params` name, each by its own name. Each value is text, a number, true or false, a list of at most 100 of them, or a list of records of at most 20 of them by name; a name holds no control character and is not `__proto__`, `constructor` or `prototype`. A value the person mapped (`{{3.x}}`) is never given to the app: it reads as missing.",
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "string",
                          "maxLength": 2000
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        },
                        {
                          "type": "array",
                          "maxItems": 100,
                          "description": "A list of values: a select of several choices.",
                          "items": {
                            "oneOf": [
                              {
                                "type": "string",
                                "maxLength": 2000
                              },
                              {
                                "type": "number"
                              },
                              {
                                "type": "boolean"
                              }
                            ]
                          }
                        },
                        {
                          "type": "array",
                          "maxItems": 100,
                          "description": "A list of records: an array of collections, each record's own values only (at most 20, by names a form may hold), nothing nested deeper.",
                          "items": {
                            "type": "object",
                            "maxProperties": 20,
                            "additionalProperties": {
                              "oneOf": [
                                {
                                  "type": "string",
                                  "maxLength": 2000
                                },
                                {
                                  "type": "number"
                                },
                                {
                                  "type": "boolean"
                                }
                              ]
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The choices, at most 1,000.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "choices",
                        "truncated"
                      ],
                      "properties": {
                        "choices": {
                          "type": "array",
                          "maxItems": 1000,
                          "items": {
                            "$ref": "#/components/schemas/FlowRpcChoice"
                          }
                        },
                        "truncated": {
                          "type": "integer",
                          "minimum": 0,
                          "description": "How many choices were left out after the first 1,000."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is not the bounded fields, or `use` is not `choices`, each within its bounds (`code` is `rpc_invalid`). The answer names the field, never its value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No such flow in this organization (`Flow not found`), or the module is not one the organization may see or does not declare the reference (`code` is `rpc_not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The flow is in the trash (`code` is `flow_trashed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "description": "The choices could not be read: the connection cannot be used, or the connected system failed or refused the read. The engine's error class is `type`; the message is redacted of the connection's credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlowRpcRefused"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/flows/{id}/rpc-fields": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "fetchFlowRpcFields",
        "tags": [
          "Flows"
        ],
        "summary": "The fields an app's RPC fetches, or a module's output fields",
        "description": "Make's dynamic fields RPC, for the builder: the fields of a select whose `ui.nestedSource`, or a collection whose `ui.specSource`, is `pack.rpc`, read from the connected system with the connection chosen for the module. It is asked as a dropdown's choices are (`POST /flows/{id}/rpc`): `rpc` is the reference the source names, exactly as written, and only a reference the module itself declares for its fields runs (a dropdown's reference is not one), for a module of an app the organization may see; `parameters` are the values of the fields the source's `params` name. The read runs for at most 40 seconds. At most 200 fields are answered, in the app's order, each a field the builder draws on its own: text, a number, yes or no, a date, or any value (a select with fixed choices, a hidden value); `skipped` counts what was no such field (no name, a name of more than 100 characters or that a form cannot hold, a list, a group, a select whose choices are fetched again, or a second field of a name), and `truncated` those left out after the first 200. A field keeps its words, its choices (at most 1,000), its default, and whether it is required, advanced, multiline or mapped; never what it would fetch or reveal. The words are the connected system's text, the same in English and Hebrew, cut (a label at 200 characters, help at 1,000) and redacted of the connection's credentials, as written or as JSON text spells them: `[redacted]` in their place; a field whose name, a choice whose value, or a default that holds one is left out. A read: it writes nothing, is not recorded in the audit log, and is answered with `Cache-Control: no-store`. It needs `flows.edit` and, for an API token, `flows:write`; it shares the dropdowns' limit of 120 asks a minute for each API token or user. A body that is not the bounded fields, or that asks for a `use` the route does not answer, answers 400 `rpc_invalid`; a module the organization does not see, or that does not declare the reference for its fields, 404 `rpc_not_found`; a flow in the trash 409 `flow_trashed`. A read that fails answers 502 `rpc_failed` with the engine's error class (`type`), never the connected system's text unredacted; a connection of another organization reads as not found. With `use` `outputs`, the module's output fields (Make's dynamic interface RPC): the reference its output schema's `source` names (FlowNodeSchemas `output.source`), with the values of the settings and inputs its `params` name. Answered the same way, except that a field may be of any type, and a collection keeps its fields and an array its records' fields (`spec`) or its plain item (`item`), down to 4 levels; at most 1,000 fields at every depth together, in the app's order, a field before its own fields. `skipped` also counts a field deeper than that (once, with what it holds); a field keeps only its words (`label`, `help`), without the app's bidi control characters. What it fetches is never saved in the flow, and a run never reads it: a step's output is what the step gives.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "x-rate-limit": {
          "requests": 120,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type",
                  "conn",
                  "rpc"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "The module's type, e.g. `slack:CreateMessage`."
                  },
                  "conn": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128,
                    "description": "The id of the connection chosen for the module."
                  },
                  "rpc": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000,
                    "description": "The reference the field's `ui.nestedSource` or `ui.specSource` names, or (with `use` `outputs`) the output schema's `source` (`FlowSourceRef.rpc`), exactly as written."
                  },
                  "use": {
                    "type": "string",
                    "enum": [
                      "fields",
                      "outputs"
                    ],
                    "default": "fields",
                    "description": "What the reference runs for: the fields a field fetches (`fields`), or the module's output fields (`outputs`)."
                  },
                  "parameters": {
                    "type": "object",
                    "maxProperties": 50,
                    "description": "The values of the fields the source's `params` name, each by its own name. Each value is text, a number, true or false, a list of at most 100 of them, or a list of records of at most 20 of them by name; a name holds no control character and is not `__proto__`, `constructor` or `prototype`. A value the person mapped (`{{3.x}}`) is never given to the app: it reads as missing.",
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "string",
                          "maxLength": 2000
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        },
                        {
                          "type": "array",
                          "maxItems": 100,
                          "description": "A list of values: a select of several choices.",
                          "items": {
                            "oneOf": [
                              {
                                "type": "string",
                                "maxLength": 2000
                              },
                              {
                                "type": "number"
                              },
                              {
                                "type": "boolean"
                              }
                            ]
                          }
                        },
                        {
                          "type": "array",
                          "maxItems": 100,
                          "description": "A list of records: an array of collections, each record's own values only (at most 20, by names a form may hold), nothing nested deeper.",
                          "items": {
                            "type": "object",
                            "maxProperties": 20,
                            "additionalProperties": {
                              "oneOf": [
                                {
                                  "type": "string",
                                  "maxLength": 2000
                                },
                                {
                                  "type": "number"
                                },
                                {
                                  "type": "boolean"
                                }
                              ]
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The fields: at most 200 (`fields`), or at most 1,000 at every depth together (`outputs`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "fields",
                        "skipped",
                        "truncated"
                      ],
                      "properties": {
                        "fields": {
                          "type": "array",
                          "maxItems": 1000,
                          "items": {
                            "$ref": "#/components/schemas/FlowFieldSpec"
                          }
                        },
                        "skipped": {
                          "type": "integer",
                          "minimum": 0,
                          "description": "How many items the app answered that are no field the builder draws (`fields`), or no output field, or one deeper than 4 levels (`outputs`)."
                        },
                        "truncated": {
                          "type": "integer",
                          "minimum": 0,
                          "description": "How many fields were left out after the first 200 (`fields`), or the first 1,000 at every depth together (`outputs`)."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is not the bounded fields, or `use` is neither `fields` nor `outputs` (`code` is `rpc_invalid`). The answer names the field, never its value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No such flow in this organization (`Flow not found`), or the module is not one the organization may see, or does not declare the reference for its fields (`code` is `rpc_not_found`), or for its outputs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The flow is in the trash (`code` is `flow_trashed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "description": "The fields could not be read: the connection cannot be used, or the connected system failed or refused the read. The engine's error class is `type`; the message is redacted of the connection's credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlowRpcRefused"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/flows/{id}/webhook": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getFlowWebhook",
        "tags": [
          "Flows"
        ],
        "summary": "Show a flow's webhook URL",
        "description": "The flow's webhook: the address its connected system posts to, and the token in it. Owners and admins only, since the token is a credential. For the same reason an API token needs `flows:write` to see it, as it does to create or rotate it; `flows:read` is not enough. A flow with no webhook answers `webhook_not_found`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "The webhook.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "A flow's webhook. Its token is the whole credential of the URL: treat it as a secret. A custom webhook's answer also carries its settings.",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "push",
                            "webhook"
                          ],
                          "description": "`push`: Priority push, the instant trigger of a flow that starts with Watch records (instant). `webhook`: a custom webhook, the instant trigger of a flow that starts with Custom webhook."
                        },
                        "token": {
                          "type": "string",
                          "description": "Treat as a secret. 43 characters."
                        },
                        "path": {
                          "type": "string",
                          "description": "Append to the API base to get the URL the connected system posts to."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "rotated_at": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "When the token was last replaced; null if never."
                        },
                        "previous_until": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "While the previous address still works, when it stops (24 hours after the latest new address); null otherwise. The previous token itself is never returned."
                        },
                        "object_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "A push webhook only. The form (Priority entity) each push is parsed by, as the flow's start module names it: from its draft when the draft starts with a push trigger, else from its saved version. Null when the module names no form yet or the form has no key."
                        },
                        "key_field": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "A push webhook only. The ids of that form's key fields, in order, comma-separated (`IVNUM,DEBIT,IVTYPE`): the fields each push must carry. Null with `object_id`."
                        },
                        "api_key_set": {
                          "type": "boolean",
                          "description": "A custom webhook only. Whether requests must carry the API key in `x-flow-apikey`. The key itself is shown once, when it is made."
                        },
                        "ip_allow": {
                          "type": "array",
                          "nullable": true,
                          "items": {
                            "type": "string"
                          },
                          "description": "A custom webhook only. The IP addresses and CIDR ranges (IPv4 or IPv6) a request may come from; null when every address may. A request from any other address is answered `400` *Access denied from this IP.*, whether the flow is on or off."
                        },
                        "data_structure_id": {
                          "type": "string",
                          "nullable": true,
                          "description": "A custom webhook only. The data structure each request is checked against: a request that does not fit it is answered `400` *Validation failed for N parameter(s).* and nothing is kept. JSON and form values are converted to the field's type; query string values are not. Only the structure's fields are kept."
                        },
                        "options": {
                          "type": "object",
                          "nullable": true,
                          "description": "A custom webhook only. `headers`: each request's headers in `__IMTHEADERS__` (`[{name, value}]`, names in lower case, the API key, cookies and the service secret as `***`); `method`: its HTTP method in `__IMTMETHOD__`; `jsonPassThrough`: a JSON body kept as text in `value`.",
                          "properties": {
                            "headers": {
                              "type": "boolean"
                            },
                            "method": {
                              "type": "boolean"
                            },
                            "jsonPassThrough": {
                              "type": "boolean"
                            }
                          },
                          "additionalProperties": false
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createFlowWebhook",
        "tags": [
          "Flows"
        ],
        "summary": "Create a flow’s webhook",
        "description": "Creates the flow's webhook and its token, for a flow that starts with a push trigger (Priority's Watch records (instant)) or with a Custom webhook, in its draft or else in its saved version; otherwise `not_push_trigger`. Its `kind` follows that trigger. A flow has one webhook: a second answers `webhook_exists` (rotate the token for a new one). A webhook made for another kind of start (the flow's start changed from Priority push to a Custom webhook, or back) is made again in its place for the new start, with a new token and none of the old kind's settings (its API key, IP allow-list, data structure and options); the old URL stops working, and this answers `201`. A flow in the trash answers `flow_trashed`. Owners and admins only; recorded in the audit log, without the token.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "201": {
            "description": "The webhook, with its token.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "A flow's webhook. Its token is the whole credential of the URL: treat it as a secret. A custom webhook's answer also carries its settings.",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "push",
                            "webhook"
                          ],
                          "description": "`push`: Priority push, the instant trigger of a flow that starts with Watch records (instant). `webhook`: a custom webhook, the instant trigger of a flow that starts with Custom webhook."
                        },
                        "token": {
                          "type": "string",
                          "description": "Treat as a secret. 43 characters."
                        },
                        "path": {
                          "type": "string",
                          "description": "Append to the API base to get the URL the connected system posts to."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "rotated_at": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "When the token was last replaced; null if never."
                        },
                        "previous_until": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "While the previous address still works, when it stops (24 hours after the latest new address); null otherwise. The previous token itself is never returned."
                        },
                        "object_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "A push webhook only. The form (Priority entity) each push is parsed by, as the flow's start module names it: from its draft when the draft starts with a push trigger, else from its saved version. Null when the module names no form yet or the form has no key."
                        },
                        "key_field": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "A push webhook only. The ids of that form's key fields, in order, comma-separated (`IVNUM,DEBIT,IVTYPE`): the fields each push must carry. Null with `object_id`."
                        },
                        "api_key_set": {
                          "type": "boolean",
                          "description": "A custom webhook only. Whether requests must carry the API key in `x-flow-apikey`. The key itself is shown once, when it is made."
                        },
                        "ip_allow": {
                          "type": "array",
                          "nullable": true,
                          "items": {
                            "type": "string"
                          },
                          "description": "A custom webhook only. The IP addresses and CIDR ranges (IPv4 or IPv6) a request may come from; null when every address may. A request from any other address is answered `400` *Access denied from this IP.*, whether the flow is on or off."
                        },
                        "data_structure_id": {
                          "type": "string",
                          "nullable": true,
                          "description": "A custom webhook only. The data structure each request is checked against: a request that does not fit it is answered `400` *Validation failed for N parameter(s).* and nothing is kept. JSON and form values are converted to the field's type; query string values are not. Only the structure's fields are kept."
                        },
                        "options": {
                          "type": "object",
                          "nullable": true,
                          "description": "A custom webhook only. `headers`: each request's headers in `__IMTHEADERS__` (`[{name, value}]`, names in lower case, the API key, cookies and the service secret as `***`); `method`: its HTTP method in `__IMTMETHOD__`; `jsonPassThrough`: a JSON body kept as text in `value`.",
                          "properties": {
                            "headers": {
                              "type": "boolean"
                            },
                            "method": {
                              "type": "boolean"
                            },
                            "jsonPassThrough": {
                              "type": "boolean"
                            }
                          },
                          "additionalProperties": false
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/{id}/webhook/rotate": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "rotateFlowWebhook",
        "tags": [
          "Flows"
        ],
        "summary": "Rotate a flow’s webhook token",
        "description": "Gives the flow's webhook a new token. The former address keeps working for 24 hours, so there is time to give the connected system the new one; `previous_until` says when it stops, and `DELETE /api/v1/flows/{id}/webhook/previous` stops it at once. Rotating again inside those 24 hours stops the first former address at once. A flow with no webhook answers `webhook_not_found`; a flow in the trash `flow_trashed`. A listening window that is open closes (`POST /api/v1/flows/{id}/webhook/listen`); a sample already kept stays. Owners and admins only; recorded in the audit log as `webhook_rotated` with `overlap_until`, without either token.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "The webhook, with its new token.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "A flow's webhook. Its token is the whole credential of the URL: treat it as a secret. A custom webhook's answer also carries its settings.",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "push",
                            "webhook"
                          ],
                          "description": "`push`: Priority push, the instant trigger of a flow that starts with Watch records (instant). `webhook`: a custom webhook, the instant trigger of a flow that starts with Custom webhook."
                        },
                        "token": {
                          "type": "string",
                          "description": "Treat as a secret. 43 characters."
                        },
                        "path": {
                          "type": "string",
                          "description": "Append to the API base to get the URL the connected system posts to."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "rotated_at": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "When the token was last replaced; null if never."
                        },
                        "previous_until": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "While the previous address still works, when it stops (24 hours after the latest new address); null otherwise. The previous token itself is never returned."
                        },
                        "object_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "A push webhook only. The form (Priority entity) each push is parsed by, as the flow's start module names it: from its draft when the draft starts with a push trigger, else from its saved version. Null when the module names no form yet or the form has no key."
                        },
                        "key_field": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "A push webhook only. The ids of that form's key fields, in order, comma-separated (`IVNUM,DEBIT,IVTYPE`): the fields each push must carry. Null with `object_id`."
                        },
                        "api_key_set": {
                          "type": "boolean",
                          "description": "A custom webhook only. Whether requests must carry the API key in `x-flow-apikey`. The key itself is shown once, when it is made."
                        },
                        "ip_allow": {
                          "type": "array",
                          "nullable": true,
                          "items": {
                            "type": "string"
                          },
                          "description": "A custom webhook only. The IP addresses and CIDR ranges (IPv4 or IPv6) a request may come from; null when every address may. A request from any other address is answered `400` *Access denied from this IP.*, whether the flow is on or off."
                        },
                        "data_structure_id": {
                          "type": "string",
                          "nullable": true,
                          "description": "A custom webhook only. The data structure each request is checked against: a request that does not fit it is answered `400` *Validation failed for N parameter(s).* and nothing is kept. JSON and form values are converted to the field's type; query string values are not. Only the structure's fields are kept."
                        },
                        "options": {
                          "type": "object",
                          "nullable": true,
                          "description": "A custom webhook only. `headers`: each request's headers in `__IMTHEADERS__` (`[{name, value}]`, names in lower case, the API key, cookies and the service secret as `***`); `method`: its HTTP method in `__IMTMETHOD__`; `jsonPassThrough`: a JSON body kept as text in `value`.",
                          "properties": {
                            "headers": {
                              "type": "boolean"
                            },
                            "method": {
                              "type": "boolean"
                            },
                            "jsonPassThrough": {
                              "type": "boolean"
                            }
                          },
                          "additionalProperties": false
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/{id}/queue": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listFlowQueue",
        "tags": [
          "Flows"
        ],
        "summary": "List a flow's queue",
        "description": "The items the flow's webhook received that no run has consumed yet, oldest first, the ones a run has included; without what they hold. At most 10,000 wait. Owners and admins only, since items hold the sender's data.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 200; default 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "The items.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "description": "An item of the queue: one request the webhook received, not yet consumed by a run.",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "claimed"
                            ],
                            "description": "`queued`: waiting for a run. `claimed`: a run has it; it is consumed when the run ends with Success or Warning, and back at the head of the queue if the run fails."
                          },
                          "run_id": {
                            "type": "string",
                            "nullable": true,
                            "description": "The run that has a claimed item."
                          },
                          "size": {
                            "type": "integer",
                            "description": "The size of what the item holds, in bytes."
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "claimed_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "clearFlowQueue",
        "tags": [
          "Flows"
        ],
        "summary": "Delete a flow's waiting items",
        "description": "Deletes every item that waits. One a run has stays: it is that run’s. Owners and admins only; recorded in the audit log.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:run"
        ],
        "responses": {
          "200": {
            "description": "How many items were deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/{id}/queue/summary": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getFlowQueueSummary",
        "tags": [
          "Flows"
        ],
        "summary": "Count a flow's waiting requests",
        "description": "How many of the requests the flow's webhook received wait for a run, so an editor can wait for one before running the flow once. An item a run has already taken is not counted. When this is 0, running once a flow that starts with a custom webhook answers 409 `queue_empty`. A count only: no item, id or content. Needs the flows.edit permission, not owner or admin.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "How many requests wait.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "waiting": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 10000,
                          "description": "The requests that wait for a run, at most 10,000 (the queue's limit)."
                        }
                      },
                      "required": [
                        "waiting"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/{id}/queue/{itemId}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        },
        {
          "name": "itemId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getFlowQueueItem",
        "tags": [
          "Flows"
        ],
        "summary": "Read an item of a flow’s queue",
        "description": "The item and what it holds (`payload`): for a Priority push, the record the request named (`objectId`, `recordId`). An item no longer in the queue answers `queue_item_not_found`. Owners and admins only.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The item.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "type": "object",
                          "description": "An item of the queue: one request the webhook received, not yet consumed by a run.",
                          "properties": {
                            "id": {
                              "type": "string"
                            },
                            "state": {
                              "type": "string",
                              "enum": [
                                "queued",
                                "claimed"
                              ],
                              "description": "`queued`: waiting for a run. `claimed`: a run has it; it is consumed when the run ends with Success or Warning, and back at the head of the queue if the run fails."
                            },
                            "run_id": {
                              "type": "string",
                              "nullable": true,
                              "description": "The run that has a claimed item."
                            },
                            "size": {
                              "type": "integer",
                              "description": "The size of what the item holds, in bytes."
                            },
                            "created_at": {
                              "type": "string",
                              "format": "date-time"
                            },
                            "claimed_at": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true
                            }
                          }
                        },
                        {
                          "type": "object",
                          "properties": {
                            "payload": {
                              "description": "What the item holds, as JSON."
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteFlowQueueItem",
        "tags": [
          "Flows"
        ],
        "summary": "Delete an item of a flow’s queue",
        "description": "Deletes an item that waits, such as one that makes every run fail. One a run has answers `queue_item_claimed`: it comes back to the head of the queue if that run fails. Owners and admins only; recorded in the audit log.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:run"
        ],
        "responses": {
          "200": {
            "description": "How many items were deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/inbound/{token}": {
      "parameters": [
        {
          "name": "token",
          "in": "path",
          "required": true,
          "description": "The token from `GET /api/v1/flows/{id}/webhook`.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "receiveFlowPush",
        "tags": [
          "Flows"
        ],
        "summary": "Receive a request for a flow’s webhook",
        "description": "Called by the customer's own system, not by you. The token in the path is the whole credential.\n\n**Priority push** (a flow that starts with Watch records (instant)): POST only. The request must name a record of the form the trigger watches; it is kept in the flow's queue and answered at once, and a run reads the record and starts from it.\n\n**Custom webhook** (a flow that starts with Custom webhook): any method (GET, POST, PUT, PATCH, DELETE, HEAD; OPTIONS answers `ok` and keeps nothing). The query string, form data, multipart or JSON body make one bundle, the query string winning on a shared key; a top-level JSON array makes one item per element, all of them or none; text is kept in `value`, an XML body and each multipart file as a file. The webhook's IP allow-list (`400` *Access denied from this IP.*), API key in `x-flow-apikey` (`401`) and data structure (`400` *Validation failed for N parameter(s).*) are checked first, and a refused request keeps nothing. Each request is logged without its body for 3 days (`GET /api/v1/flows/{id}/webhook/logs`).\n\nEach item starts one run, the oldest first: at once when the flow's `maximum_runs_per_minute` and Process data in order allow, otherwise within a minute of when they do. `200 Accepted` means queued, not run: watch the flow's runs for the outcome. A run that ends with an error puts its item back at the head of the queue and turns the flow off, unless the flow has a Webhook response (below); delete the item from the queue, or fix what made it fail, then turn the flow on again. A flow that is off answers `404` and keeps nothing.\n\n**A flow that answers its caller** (a Custom webhook flow with a Webhook response, running Immediately, not processing data in order): the request waits for the flow's run, at most 180 seconds, and gets the run's first answer: the status (200–599), body and headers its Webhook response set, with `x-flow-run-id` naming the run. It gets `200 Accepted` when the run ended without answering, or nothing answered in time (the run goes on), and `500` *Scenario failed to complete.* when the run failed first. When the run cannot start at once (the flow's `maximum_runs_per_minute`, or a back-off), it answers `429` *Too many requests for scenario* and keeps nothing. A JSON array is answered by its first item's run. Any flow with a Webhook response (whatever its schedule or order) does not put a failed item back and is not turned off by an ordinary error: the item is consumed. An error that stops the flow from running at all (`BlueprintValidationError`, `DataSizeLimitExceededError`, `InconsistencyError`, `OperationsLimitExceededError`) still turns it off, as does the last failed automatic retry of an incomplete run.\n\nEvery answer carries `Content-Security-Policy: sandbox; default-src 'none'; frame-ancestors 'none'` and `X-Content-Type-Options: nosniff`. A Webhook response cannot set `Set-Cookie`, `Location`, `Access-Control-*`, `Content-Length`, `Transfer-Encoding`, `Connection`, `Upgrade`, `Host`, `Strict-Transport-Security`, `Clear-Site-Data` or `Content-Security-Policy`.\n\nLimits per webhook: 5 MB a request, 300 requests per 10 seconds (then `429` with `Retry-After: 10`), 10,000 items waiting in the queue (then `400` *Queue is full*).",
        "security": [],
        "x-rate-limit": {
          "requests": 300,
          "seconds": 10
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "description": "Whatever the connected system sends. For Priority BPM, an array holding one object: the record's fields under its form's name."
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "description": "A custom webhook only."
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "description": "A custom webhook only. Each file part is kept as a file."
              }
            },
            "text/plain": {
              "schema": {
                "type": "string",
                "description": "A custom webhook only: kept in `value`."
              }
            },
            "application/xml": {
              "schema": {
                "type": "string",
                "description": "A custom webhook only: kept as a file in `value`."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted and queued; from a flow that answers its caller, also the answer when its run ended without one, or none came within 180 seconds.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "Accepted"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Push: malformed JSON, or the queue is full (`Queue is full`). Custom webhook (text): `Access denied from this IP.`, `Validation failed for N parameter(s).`, `Queue is full`, or a body that cannot be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "A custom webhook: the API key is missing or wrong.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "Unauthorized."
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No flow answers to that token now. Deliberately the same for an unknown token, a flow that is off, and every other reason. The one exception: while an owner or admin listens for a sample call (`POST /api/v1/flows/{id}/webhook/listen`), a flow that is off keeps the next call that passes the webhook's IP allow-list and API key as the sample, answers it `200 Accepted` and runs nothing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The request is larger than 5 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The request does not name a record of the watched form.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "description": "A flow that answers its caller: its run failed before it answered.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "Scenario failed to complete."
                  ]
                }
              }
            }
          },
          "502": {
            "description": "The request could not be read as the connected system's shape.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "A flow that answers its caller: the status (200–599), headers and body its Webhook response set.",
            "headers": {
              "x-flow-run-id": {
                "description": "The run that answered.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "*/*": {
                "schema": {}
              }
            }
          }
        },
        "parameters": [
          {
            "name": "x-flow-apikey",
            "in": "header",
            "required": false,
            "description": "A custom webhook with an API key: the key.",
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          }
        ]
      },
      "get": {
        "operationId": "receiveFlowWebhookGet",
        "tags": [
          "Flows"
        ],
        "summary": "Receive a GET request for a flow’s custom webhook",
        "description": "A custom webhook takes any method: see `POST /api/v1/flows/inbound/{token}`. The method is in `__IMTMETHOD__` when the webhook's options ask for it.",
        "security": [],
        "x-rate-limit": {
          "requests": 300,
          "seconds": 10
        },
        "parameters": [
          {
            "name": "x-flow-apikey",
            "in": "header",
            "required": false,
            "description": "A custom webhook with an API key: the key.",
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Accepted and queued.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "Accepted"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`Access denied from this IP.`, `Validation failed for N parameter(s).`, `Queue is full`, or a body that cannot be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or wrong.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "Unauthorized."
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No flow answers to that token now. Deliberately the same for an unknown token, a flow that is off, a push webhook (which takes POST only), and every other reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The request is larger than 5 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "put": {
        "operationId": "receiveFlowWebhookPut",
        "tags": [
          "Flows"
        ],
        "summary": "Receive a PUT request for a flow’s custom webhook",
        "description": "A custom webhook takes any method: see `POST /api/v1/flows/inbound/{token}`. The method is in `__IMTMETHOD__` when the webhook's options ask for it.",
        "security": [],
        "x-rate-limit": {
          "requests": 300,
          "seconds": 10
        },
        "parameters": [
          {
            "name": "x-flow-apikey",
            "in": "header",
            "required": false,
            "description": "A custom webhook with an API key: the key.",
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Accepted and queued.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "Accepted"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`Access denied from this IP.`, `Validation failed for N parameter(s).`, `Queue is full`, or a body that cannot be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or wrong.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "Unauthorized."
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No flow answers to that token now. Deliberately the same for an unknown token, a flow that is off, a push webhook (which takes POST only), and every other reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The request is larger than 5 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "description": "As for POST."
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "receiveFlowWebhookPatch",
        "tags": [
          "Flows"
        ],
        "summary": "Receive a PATCH request for a flow’s custom webhook",
        "description": "A custom webhook takes any method: see `POST /api/v1/flows/inbound/{token}`. The method is in `__IMTMETHOD__` when the webhook's options ask for it.",
        "security": [],
        "x-rate-limit": {
          "requests": 300,
          "seconds": 10
        },
        "parameters": [
          {
            "name": "x-flow-apikey",
            "in": "header",
            "required": false,
            "description": "A custom webhook with an API key: the key.",
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Accepted and queued.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "Accepted"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`Access denied from this IP.`, `Validation failed for N parameter(s).`, `Queue is full`, or a body that cannot be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or wrong.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "Unauthorized."
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No flow answers to that token now. Deliberately the same for an unknown token, a flow that is off, a push webhook (which takes POST only), and every other reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The request is larger than 5 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "description": "As for POST."
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "receiveFlowWebhookDelete",
        "tags": [
          "Flows"
        ],
        "summary": "Receive a DELETE request for a flow’s custom webhook",
        "description": "A custom webhook takes any method: see `POST /api/v1/flows/inbound/{token}`. The method is in `__IMTMETHOD__` when the webhook's options ask for it.",
        "security": [],
        "x-rate-limit": {
          "requests": 300,
          "seconds": 10
        },
        "parameters": [
          {
            "name": "x-flow-apikey",
            "in": "header",
            "required": false,
            "description": "A custom webhook with an API key: the key.",
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Accepted and queued.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "Accepted"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`Access denied from this IP.`, `Validation failed for N parameter(s).`, `Queue is full`, or a body that cannot be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or wrong.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "Unauthorized."
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No flow answers to that token now. Deliberately the same for an unknown token, a flow that is off, a push webhook (which takes POST only), and every other reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The request is larger than 5 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "description": "As for POST."
              }
            }
          }
        }
      },
      "head": {
        "operationId": "receiveFlowWebhookHead",
        "tags": [
          "Flows"
        ],
        "summary": "Receive a HEAD request for a flow’s custom webhook",
        "description": "A custom webhook takes any method: see `POST /api/v1/flows/inbound/{token}`. The method is in `__IMTMETHOD__` when the webhook's options ask for it.",
        "security": [],
        "x-rate-limit": {
          "requests": 300,
          "seconds": 10
        },
        "parameters": [
          {
            "name": "x-flow-apikey",
            "in": "header",
            "required": false,
            "description": "A custom webhook with an API key: the key.",
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Accepted and queued.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "Accepted"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`Access denied from this IP.`, `Validation failed for N parameter(s).`, `Queue is full`, or a body that cannot be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or wrong.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "Unauthorized."
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No flow answers to that token now. Deliberately the same for an unknown token, a flow that is off, a push webhook (which takes POST only), and every other reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The request is larger than 5 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "options": {
        "operationId": "receiveFlowWebhookOptions",
        "tags": [
          "Flows"
        ],
        "summary": "Receive a OPTIONS request for a flow’s custom webhook",
        "description": "Answers `ok` and keeps nothing.",
        "security": [],
        "x-rate-limit": {
          "requests": 300,
          "seconds": 10
        },
        "parameters": [
          {
            "name": "x-flow-apikey",
            "in": "header",
            "required": false,
            "description": "A custom webhook with an API key: the key.",
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The webhook takes requests.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "ok"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`Access denied from this IP.`, `Validation failed for N parameter(s).`, `Queue is full`, or a body that cannot be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or wrong.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "Unauthorized."
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No flow answers to that token now. Deliberately the same for an unknown token, a flow that is off, a push webhook (which takes POST only), and every other reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The request is larger than 5 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/flows/{id}/webhook/settings": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "patch": {
        "operationId": "setFlowWebhookSettings",
        "tags": [
          "Flows"
        ],
        "summary": "Set a custom webhook’s settings",
        "description": "Sets the settings the body names (null clears one) and keeps the others: the IP allow-list, the data structure requests are checked against, and the options. Recorded in the audit log by the names of the settings changed, never their values. Owners and admins only. A push webhook has none of these settings (`not_custom_webhook`); a flow with no webhook answers `webhook_not_found`, one in the trash `flow_trashed`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "ip_allow": {
                    "type": "array",
                    "nullable": true,
                    "items": {
                      "type": "string"
                    },
                    "description": "A custom webhook only. The IP addresses and CIDR ranges (IPv4 or IPv6) a request may come from; null when every address may. A request from any other address is answered `400` *Access denied from this IP.*, whether the flow is on or off."
                  },
                  "data_structure_id": {
                    "type": "string",
                    "nullable": true,
                    "description": "A custom webhook only. The data structure each request is checked against: a request that does not fit it is answered `400` *Validation failed for N parameter(s).* and nothing is kept. JSON and form values are converted to the field's type; query string values are not. Only the structure's fields are kept."
                  },
                  "options": {
                    "type": "object",
                    "nullable": true,
                    "description": "A custom webhook only. `headers`: each request's headers in `__IMTHEADERS__` (`[{name, value}]`, names in lower case, the API key, cookies and the service secret as `***`); `method`: its HTTP method in `__IMTMETHOD__`; `jsonPassThrough`: a JSON body kept as text in `value`.",
                    "properties": {
                      "headers": {
                        "type": "boolean"
                      },
                      "method": {
                        "type": "boolean"
                      },
                      "jsonPassThrough": {
                        "type": "boolean"
                      }
                    },
                    "additionalProperties": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The webhook, with its settings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "A flow's webhook. Its token is the whole credential of the URL: treat it as a secret. A custom webhook's answer also carries its settings.",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "push",
                            "webhook"
                          ],
                          "description": "`push`: Priority push, the instant trigger of a flow that starts with Watch records (instant). `webhook`: a custom webhook, the instant trigger of a flow that starts with Custom webhook."
                        },
                        "token": {
                          "type": "string",
                          "description": "Treat as a secret. 43 characters."
                        },
                        "path": {
                          "type": "string",
                          "description": "Append to the API base to get the URL the connected system posts to."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "rotated_at": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "When the token was last replaced; null if never."
                        },
                        "previous_until": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "While the previous address still works, when it stops (24 hours after the latest new address); null otherwise. The previous token itself is never returned."
                        },
                        "api_key_set": {
                          "type": "boolean",
                          "description": "A custom webhook only. Whether requests must carry the API key in `x-flow-apikey`. The key itself is shown once, when it is made."
                        },
                        "ip_allow": {
                          "type": "array",
                          "nullable": true,
                          "items": {
                            "type": "string"
                          },
                          "description": "A custom webhook only. The IP addresses and CIDR ranges (IPv4 or IPv6) a request may come from; null when every address may. A request from any other address is answered `400` *Access denied from this IP.*, whether the flow is on or off."
                        },
                        "data_structure_id": {
                          "type": "string",
                          "nullable": true,
                          "description": "A custom webhook only. The data structure each request is checked against: a request that does not fit it is answered `400` *Validation failed for N parameter(s).* and nothing is kept. JSON and form values are converted to the field's type; query string values are not. Only the structure's fields are kept."
                        },
                        "options": {
                          "type": "object",
                          "nullable": true,
                          "description": "A custom webhook only. `headers`: each request's headers in `__IMTHEADERS__` (`[{name, value}]`, names in lower case, the API key, cookies and the service secret as `***`); `method`: its HTTP method in `__IMTMETHOD__`; `jsonPassThrough`: a JSON body kept as text in `value`.",
                          "properties": {
                            "headers": {
                              "type": "boolean"
                            },
                            "method": {
                              "type": "boolean"
                            },
                            "jsonPassThrough": {
                              "type": "boolean"
                            }
                          },
                          "additionalProperties": false
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_ip_allow` (an entry that is not an IP address or CIDR range, or more than 100), `invalid_options`, `invalid_settings`, or `data_structure_not_found` (not one of this organization's).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/{id}/webhook/api-key": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "createFlowWebhookApiKey",
        "tags": [
          "Flows"
        ],
        "summary": "Make a custom webhook’s API key",
        "description": "Makes a new API key and shows it once, in this answer only: SignFlow keeps a digest of it, never the key. From now on a request must carry it in the `x-flow-apikey` header, or it is answered `401`; a key made before stops working. Recorded in the audit log, without the key. Owners and admins only. A push webhook has none of these settings (`not_custom_webhook`); a flow with no webhook answers `webhook_not_found`, one in the trash `flow_trashed`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "201": {
            "description": "The key. Store it now: it is never shown again.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "api_key": {
                          "type": "string",
                          "description": "Treat as a secret. 43 characters."
                        },
                        "header": {
                          "type": "string",
                          "enum": [
                            "x-flow-apikey"
                          ],
                          "description": "The header a request carries it in."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      },
      "delete": {
        "operationId": "clearFlowWebhookApiKey",
        "tags": [
          "Flows"
        ],
        "summary": "Remove a custom webhook’s API key",
        "description": "Requests no longer need an API key. Recorded in the audit log. Owners and admins only. A push webhook has none of these settings (`not_custom_webhook`); a flow with no webhook answers `webhook_not_found`, one in the trash `flow_trashed`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "The webhook, with its settings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "A flow's webhook. Its token is the whole credential of the URL: treat it as a secret. A custom webhook's answer also carries its settings.",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "push",
                            "webhook"
                          ],
                          "description": "`push`: Priority push, the instant trigger of a flow that starts with Watch records (instant). `webhook`: a custom webhook, the instant trigger of a flow that starts with Custom webhook."
                        },
                        "token": {
                          "type": "string",
                          "description": "Treat as a secret. 43 characters."
                        },
                        "path": {
                          "type": "string",
                          "description": "Append to the API base to get the URL the connected system posts to."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "rotated_at": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "When the token was last replaced; null if never."
                        },
                        "previous_until": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "While the previous address still works, when it stops (24 hours after the latest new address); null otherwise. The previous token itself is never returned."
                        },
                        "api_key_set": {
                          "type": "boolean",
                          "description": "A custom webhook only. Whether requests must carry the API key in `x-flow-apikey`. The key itself is shown once, when it is made."
                        },
                        "ip_allow": {
                          "type": "array",
                          "nullable": true,
                          "items": {
                            "type": "string"
                          },
                          "description": "A custom webhook only. The IP addresses and CIDR ranges (IPv4 or IPv6) a request may come from; null when every address may. A request from any other address is answered `400` *Access denied from this IP.*, whether the flow is on or off."
                        },
                        "data_structure_id": {
                          "type": "string",
                          "nullable": true,
                          "description": "A custom webhook only. The data structure each request is checked against: a request that does not fit it is answered `400` *Validation failed for N parameter(s).* and nothing is kept. JSON and form values are converted to the field's type; query string values are not. Only the structure's fields are kept."
                        },
                        "options": {
                          "type": "object",
                          "nullable": true,
                          "description": "A custom webhook only. `headers`: each request's headers in `__IMTHEADERS__` (`[{name, value}]`, names in lower case, the API key, cookies and the service secret as `***`); `method`: its HTTP method in `__IMTMETHOD__`; `jsonPassThrough`: a JSON body kept as text in `value`.",
                          "properties": {
                            "headers": {
                              "type": "boolean"
                            },
                            "method": {
                              "type": "boolean"
                            },
                            "jsonPassThrough": {
                              "type": "boolean"
                            }
                          },
                          "additionalProperties": false
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/{id}/webhook/logs": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listFlowWebhookLogs",
        "tags": [
          "Flows"
        ],
        "summary": "List a flow’s webhook requests",
        "description": "The requests the flow's custom webhook answered in the last 3 days, newest first: when, the method, the status answered, the body's size in bytes, the caller's IP address and user agent. Never the body, the headers, the query string or the token. A request to a flow that is off is not logged, except a refusal by the IP allow-list or the API key, and, while a listening window is open (`POST /api/v1/flows/{id}/webhook/listen`), the call it keeps or a body that cannot be read. Owners and admins only: the address and the user agent are personal data.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 200; default 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "0 to 10,000; default 0.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 10000,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The requests.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "method": {
                            "type": "string"
                          },
                          "status": {
                            "type": "integer"
                          },
                          "size": {
                            "type": "integer",
                            "description": "The body's size in bytes."
                          },
                          "ip": {
                            "type": "string",
                            "nullable": true
                          },
                          "user_agent": {
                            "type": "string",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/{id}/webhook/listen": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "listenFlowWebhook",
        "tags": [
          "Flows"
        ],
        "summary": "Listen for a sample call",
        "description": "Opens a 5-minute window on the flow's custom webhook. The next call that passes the webhook's IP allow-list and API key is kept as the webhook's sample and answered `200 Accepted`. On a flow that is off nothing runs for it, and every other call is still answered `404` and not kept. On a flow that is on, the call is kept and also runs as usual. Read the sample with `GET /api/v1/flows/{id}/webhook/sample`, for example to make the webhook's data structure from it. Listening again opens a new window and deletes the sample kept before. Stopping (`DELETE`), rotating the webhook's token and turning the flow on close the window. Owners and admins only; at most 10 a minute. Refused for a flow in the trash (`flow_trashed`), a flow with no webhook (`webhook_not_found`), a webhook that is not a custom webhook (`not_custom_webhook`), and a flow that keeps no data (`flow_confidential`). Recorded in the audit log, without the token.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The window is open: what listening has now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "What listening for a sample call has now, for the flow's custom webhook.",
                      "properties": {
                        "state": {
                          "type": "string",
                          "enum": [
                            "listening",
                            "captured",
                            "expired",
                            "none"
                          ],
                          "description": "`listening`: a window is open until `listen_until` and no call has come. `captured`: a call came and is kept below. `expired`: a window ended with nothing kept (no call came in its 5 minutes, or the sample's 24 hours are over). `none`: no window was opened, or it was stopped."
                        },
                        "listen_until": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "When the open window closes; null unless `listening`."
                        },
                        "captured_at": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "When the call came; null unless `captured`."
                        },
                        "method": {
                          "type": "string",
                          "nullable": true,
                          "description": "The call's HTTP method."
                        },
                        "content_type": {
                          "type": "string",
                          "nullable": true,
                          "description": "The call's `Content-Type`, at most 255 characters."
                        },
                        "size": {
                          "type": "integer",
                          "nullable": true,
                          "description": "The call's body, in bytes."
                        },
                        "too_large": {
                          "type": "boolean",
                          "description": "The call came, but what it made was over 512 KiB, so only its method, content type and size were kept."
                        },
                        "payload": {
                          "nullable": true,
                          "description": "The first item the call made, as the flow's trigger would give it: the query string and the body's fields, with `__IMTHEADERS__` and `__IMTMETHOD__` when the webhook's options ask for them (the API key, cookies and the service secret as `***`). A file is its name, type and size; its bytes are never kept. Values are in the queue's stored form, as `GET /api/v1/flows/{id}/queue/{itemId}` gives them. Null unless `captured`, and when `too_large`. Readable for 24 hours after the call; the daily cleanup then deletes it."
                        }
                      },
                      "required": [
                        "state",
                        "listen_until",
                        "captured_at",
                        "method",
                        "content_type",
                        "size",
                        "too_large",
                        "payload"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "stopFlowWebhookListen",
        "tags": [
          "Flows"
        ],
        "summary": "Stop listening and discard the sample",
        "description": "Closes the flow's listening window and deletes the sample it kept; afterwards listening reads `none`. Nothing open and nothing kept is not an error. Owners and admins only. Refused for a flow in the trash (`flow_trashed`), a flow with no webhook (`webhook_not_found`) and a webhook that is not a custom webhook (`not_custom_webhook`). A window closed and a sample deleted are each recorded in the audit log.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "What listening has now: `none`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "What listening for a sample call has now, for the flow's custom webhook.",
                      "properties": {
                        "state": {
                          "type": "string",
                          "enum": [
                            "listening",
                            "captured",
                            "expired",
                            "none"
                          ],
                          "description": "`listening`: a window is open until `listen_until` and no call has come. `captured`: a call came and is kept below. `expired`: a window ended with nothing kept (no call came in its 5 minutes, or the sample's 24 hours are over). `none`: no window was opened, or it was stopped."
                        },
                        "listen_until": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "When the open window closes; null unless `listening`."
                        },
                        "captured_at": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "When the call came; null unless `captured`."
                        },
                        "method": {
                          "type": "string",
                          "nullable": true,
                          "description": "The call's HTTP method."
                        },
                        "content_type": {
                          "type": "string",
                          "nullable": true,
                          "description": "The call's `Content-Type`, at most 255 characters."
                        },
                        "size": {
                          "type": "integer",
                          "nullable": true,
                          "description": "The call's body, in bytes."
                        },
                        "too_large": {
                          "type": "boolean",
                          "description": "The call came, but what it made was over 512 KiB, so only its method, content type and size were kept."
                        },
                        "payload": {
                          "nullable": true,
                          "description": "The first item the call made, as the flow's trigger would give it: the query string and the body's fields, with `__IMTHEADERS__` and `__IMTMETHOD__` when the webhook's options ask for them (the API key, cookies and the service secret as `***`). A file is its name, type and size; its bytes are never kept. Values are in the queue's stored form, as `GET /api/v1/flows/{id}/queue/{itemId}` gives them. Null unless `captured`, and when `too_large`. Readable for 24 hours after the call; the daily cleanup then deletes it."
                        }
                      },
                      "required": [
                        "state",
                        "listen_until",
                        "captured_at",
                        "method",
                        "content_type",
                        "size",
                        "too_large",
                        "payload"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/{id}/webhook/sample": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getFlowWebhookSample",
        "tags": [
          "Flows"
        ],
        "summary": "Read the sample call",
        "description": "What listening for a sample call has now: whether a window is open, and the call it kept, if any. The sample holds the sender's data, so it is for owners and admins only and is never cached. It can be read for 24 hours after the call; the daily cleanup then deletes it. Refused for a flow in the trash (`flow_trashed`), a flow with no webhook (`webhook_not_found`) and a webhook that is not a custom webhook (`not_custom_webhook`).",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "What listening has now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "What listening for a sample call has now, for the flow's custom webhook.",
                      "properties": {
                        "state": {
                          "type": "string",
                          "enum": [
                            "listening",
                            "captured",
                            "expired",
                            "none"
                          ],
                          "description": "`listening`: a window is open until `listen_until` and no call has come. `captured`: a call came and is kept below. `expired`: a window ended with nothing kept (no call came in its 5 minutes, or the sample's 24 hours are over). `none`: no window was opened, or it was stopped."
                        },
                        "listen_until": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "When the open window closes; null unless `listening`."
                        },
                        "captured_at": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "When the call came; null unless `captured`."
                        },
                        "method": {
                          "type": "string",
                          "nullable": true,
                          "description": "The call's HTTP method."
                        },
                        "content_type": {
                          "type": "string",
                          "nullable": true,
                          "description": "The call's `Content-Type`, at most 255 characters."
                        },
                        "size": {
                          "type": "integer",
                          "nullable": true,
                          "description": "The call's body, in bytes."
                        },
                        "too_large": {
                          "type": "boolean",
                          "description": "The call came, but what it made was over 512 KiB, so only its method, content type and size were kept."
                        },
                        "payload": {
                          "nullable": true,
                          "description": "The first item the call made, as the flow's trigger would give it: the query string and the body's fields, with `__IMTHEADERS__` and `__IMTMETHOD__` when the webhook's options ask for them (the API key, cookies and the service secret as `***`). A file is its name, type and size; its bytes are never kept. Values are in the queue's stored form, as `GET /api/v1/flows/{id}/queue/{itemId}` gives them. Null unless `captured`, and when `too_large`. Readable for 24 hours after the call; the daily cleanup then deletes it."
                        }
                      },
                      "required": [
                        "state",
                        "listen_until",
                        "captured_at",
                        "method",
                        "content_type",
                        "size",
                        "too_large",
                        "payload"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/flows/{id}/webhook/previous": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "delete": {
        "operationId": "endFlowWebhookOverlap",
        "tags": [
          "Flows"
        ],
        "summary": "Stop the old webhook address now",
        "description": "After a rotation the former address keeps working for 24 hours (`previous_until`). This ends that at once: from now on the former address answers 404, as an unknown one does. With no former address still in time it changes nothing and is not an error. Allowed on a flow in the trash too. Owners and admins only. Recorded in the audit log as `webhook_overlap_ended`, without either token.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "The webhook, as `GET /api/v1/flows/{id}/webhook` answers it, with `previous_until` null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "The flow's webhook. A push webhook also carries `object_id` and `key_field`, a custom webhook its settings, as in `GET /api/v1/flows/{id}/webhook`.",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "push",
                            "webhook"
                          ]
                        },
                        "token": {
                          "type": "string",
                          "description": "Treat as a secret. 43 characters."
                        },
                        "path": {
                          "type": "string",
                          "description": "Append to the API base to get the URL the connected system posts to."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "rotated_at": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "When the token was last replaced; null if never."
                        },
                        "previous_until": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true,
                          "description": "Null: no former address works any more."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/schedule/preview": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "previewFlowSchedule",
        "tags": [
          "Flows"
        ],
        "summary": "Preview a schedule's next runs",
        "description": "The next runs a schedule would start if a flow were turned on now, computed by the same engine that runs flows (windows, daylight saving and the search limit included). Nothing is stored, no flow is read and nothing is audited. `runs` is empty for Immediately, On demand, a Once date in the past, and a schedule whose windows its times never meet. Needs the `flows.edit` permission; at most 60 a minute.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "schedule"
                ],
                "properties": {
                  "schedule": {
                    "$ref": "#/components/schemas/FlowSchedule"
                  },
                  "timezone": {
                    "type": "string",
                    "nullable": true,
                    "description": "An IANA time zone; null or left out: the organization's (else UTC)."
                  },
                  "count": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 5,
                    "default": 3,
                    "description": "How many runs to list; a value outside 1 to 5 is brought inside it."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The next runs, earliest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "runs": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "format": "date-time"
                          }
                        },
                        "timezone": {
                          "type": "string",
                          "description": "The zone the schedule was read in."
                        }
                      },
                      "required": [
                        "runs",
                        "timezone"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/data-structures": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listDataStructures",
        "tags": [
          "Flows"
        ],
        "summary": "List the organization's data structures",
        "description": "By name, without their fields. Any member of an organization with the flow engine.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 200; default 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of data structures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "strict": {
                            "type": "boolean",
                            "description": "Strict: a payload with a field the structure does not define is refused (at a webhook)."
                          },
                          "created_by": {
                            "type": "string",
                            "nullable": true
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "createDataStructure",
        "tags": [
          "Flows"
        ],
        "summary": "Create a data structure",
        "description": "Creates a data structure from its name, its fields and strict mode. Owners and admins only; recorded in the audit log with its id and name, never its fields.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "spec"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Trimmed; 1 to 200 characters."
                  },
                  "spec": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "description": "One field of a data structure, in Make's parameter shape. `type` is one of text, number, boolean, date, buffer, collection, array. `spec`: a collection's fields (a list of items), or an array's item (one item, its `name` optional, or a list of fields for items that are collections); absent, any fields or items. Unknown properties are refused; a property set to null is left out.",
                      "required": [
                        "name",
                        "type"
                      ],
                      "properties": {
                        "name": {
                          "type": "string",
                          "description": "Unique on its level; at most 256 characters; not __proto__, constructor or prototype."
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "text",
                            "number",
                            "boolean",
                            "date",
                            "buffer",
                            "collection",
                            "array"
                          ]
                        },
                        "label": {
                          "type": "string"
                        },
                        "help": {
                          "type": "string"
                        },
                        "required": {
                          "type": "boolean"
                        },
                        "default": {
                          "description": "Any JSON value, nested at most 64 levels."
                        },
                        "multiline": {
                          "type": "boolean"
                        },
                        "spec": {
                          "description": "A list of items, or (for an array) one item.",
                          "oneOf": [
                            {
                              "type": "array",
                              "items": {
                                "type": "object"
                              }
                            },
                            {
                              "type": "object"
                            }
                          ]
                        }
                      }
                    },
                    "description": "The fields, checked before they are stored."
                  },
                  "strict": {
                    "type": "boolean",
                    "description": "Default false."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The data structure.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "An organization's data structure: the fields a JSON module, a data store or a webhook reads.",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "strict": {
                          "type": "boolean",
                          "description": "Strict: a payload with a field the structure does not define is refused (at a webhook)."
                        },
                        "created_by": {
                          "type": "string",
                          "nullable": true
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "spec": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "description": "One field of a data structure, in Make's parameter shape. `type` is one of text, number, boolean, date, buffer, collection, array. `spec`: a collection's fields (a list of items), or an array's item (one item, its `name` optional, or a list of fields for items that are collections); absent, any fields or items. Unknown properties are refused; a property set to null is left out.",
                            "required": [
                              "name",
                              "type"
                            ],
                            "properties": {
                              "name": {
                                "type": "string",
                                "description": "Unique on its level; at most 256 characters; not __proto__, constructor or prototype."
                              },
                              "type": {
                                "type": "string",
                                "enum": [
                                  "text",
                                  "number",
                                  "boolean",
                                  "date",
                                  "buffer",
                                  "collection",
                                  "array"
                                ]
                              },
                              "label": {
                                "type": "string"
                              },
                              "help": {
                                "type": "string"
                              },
                              "required": {
                                "type": "boolean"
                              },
                              "default": {
                                "description": "Any JSON value, nested at most 64 levels."
                              },
                              "multiline": {
                                "type": "boolean"
                              },
                              "spec": {
                                "description": "A list of items, or (for an array) one item.",
                                "oneOf": [
                                  {
                                    "type": "array",
                                    "items": {
                                      "type": "object"
                                    }
                                  },
                                  {
                                    "type": "object"
                                  }
                                ]
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: `invalid_name`, `invalid_spec` (with `errors`) or `invalid_strict`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    },
                    "errors": {
                      "type": "array",
                      "description": "`invalid_spec` only: each problem, with its path from `spec` (at most 50).",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "description": "The items are over 1,900,000 bytes as JSON (`data_structure_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/data-structures/generate": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "generateDataStructure",
        "tags": [
          "Flows"
        ],
        "summary": "Generate a data structure's fields from a sample",
        "description": "Reads a pasted JSON sample and answers the fields it describes, to create or change a structure with. It stores nothing. One field per key in the order first met; a nested object is a collection; an array's items merge into one item; a JSON text is text. The root is an object or an array of objects. Any member of an organization with the flow engine.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sample"
                ],
                "properties": {
                  "sample": {
                    "type": "string",
                    "description": "The JSON text."
                  },
                  "format": {
                    "type": "string",
                    "enum": [
                      "json"
                    ],
                    "description": "Only JSON (default)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The generated fields.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "spec": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "description": "One field of a data structure, in Make's parameter shape. `type` is one of text, number, boolean, date, buffer, collection, array. `spec`: a collection's fields (a list of items), or an array's item (one item, its `name` optional, or a list of fields for items that are collections); absent, any fields or items. Unknown properties are refused; a property set to null is left out.",
                            "required": [
                              "name",
                              "type"
                            ],
                            "properties": {
                              "name": {
                                "type": "string",
                                "description": "Unique on its level; at most 256 characters; not __proto__, constructor or prototype."
                              },
                              "type": {
                                "type": "string",
                                "enum": [
                                  "text",
                                  "number",
                                  "boolean",
                                  "date",
                                  "buffer",
                                  "collection",
                                  "array"
                                ]
                              },
                              "label": {
                                "type": "string"
                              },
                              "help": {
                                "type": "string"
                              },
                              "required": {
                                "type": "boolean"
                              },
                              "default": {
                                "description": "Any JSON value, nested at most 64 levels."
                              },
                              "multiline": {
                                "type": "boolean"
                              },
                              "spec": {
                                "description": "A list of items, or (for an array) one item.",
                                "oneOf": [
                                  {
                                    "type": "array",
                                    "items": {
                                      "type": "object"
                                    }
                                  },
                                  {
                                    "type": "object"
                                  }
                                ]
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: `invalid_sample` (not JSON, or no object at its root) or `unsupported_format`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    },
                    "errors": {
                      "type": "array",
                      "description": "`invalid_spec` only: each problem, with its path from `spec` (at most 50).",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "description": "The items are over 1,900,000 bytes as JSON (`data_structure_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/data-structures/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getDataStructure",
        "tags": [
          "Flows"
        ],
        "summary": "Read a data structure",
        "description": "A data structure with its fields. Another organization's structure answers `data_structure_not_found`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The data structure.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "An organization's data structure: the fields a JSON module, a data store or a webhook reads.",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "strict": {
                          "type": "boolean",
                          "description": "Strict: a payload with a field the structure does not define is refused (at a webhook)."
                        },
                        "created_by": {
                          "type": "string",
                          "nullable": true
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "spec": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "description": "One field of a data structure, in Make's parameter shape. `type` is one of text, number, boolean, date, buffer, collection, array. `spec`: a collection's fields (a list of items), or an array's item (one item, its `name` optional, or a list of fields for items that are collections); absent, any fields or items. Unknown properties are refused; a property set to null is left out.",
                            "required": [
                              "name",
                              "type"
                            ],
                            "properties": {
                              "name": {
                                "type": "string",
                                "description": "Unique on its level; at most 256 characters; not __proto__, constructor or prototype."
                              },
                              "type": {
                                "type": "string",
                                "enum": [
                                  "text",
                                  "number",
                                  "boolean",
                                  "date",
                                  "buffer",
                                  "collection",
                                  "array"
                                ]
                              },
                              "label": {
                                "type": "string"
                              },
                              "help": {
                                "type": "string"
                              },
                              "required": {
                                "type": "boolean"
                              },
                              "default": {
                                "description": "Any JSON value, nested at most 64 levels."
                              },
                              "multiline": {
                                "type": "boolean"
                              },
                              "spec": {
                                "description": "A list of items, or (for an array) one item.",
                                "oneOf": [
                                  {
                                    "type": "array",
                                    "items": {
                                      "type": "object"
                                    }
                                  },
                                  {
                                    "type": "object"
                                  }
                                ]
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateDataStructure",
        "tags": [
          "Flows"
        ],
        "summary": "Change a data structure",
        "description": "Sets its name, fields or strict mode, whichever are given, in place: flows read the current structure when they run. Owners and admins only; recorded in the audit log with its id, name and which parts changed, never its fields. Nothing to change answers `nothing_to_change`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Trimmed; 1 to 200 characters."
                  },
                  "spec": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "description": "One field of a data structure, in Make's parameter shape. `type` is one of text, number, boolean, date, buffer, collection, array. `spec`: a collection's fields (a list of items), or an array's item (one item, its `name` optional, or a list of fields for items that are collections); absent, any fields or items. Unknown properties are refused; a property set to null is left out.",
                      "required": [
                        "name",
                        "type"
                      ],
                      "properties": {
                        "name": {
                          "type": "string",
                          "description": "Unique on its level; at most 256 characters; not __proto__, constructor or prototype."
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "text",
                            "number",
                            "boolean",
                            "date",
                            "buffer",
                            "collection",
                            "array"
                          ]
                        },
                        "label": {
                          "type": "string"
                        },
                        "help": {
                          "type": "string"
                        },
                        "required": {
                          "type": "boolean"
                        },
                        "default": {
                          "description": "Any JSON value, nested at most 64 levels."
                        },
                        "multiline": {
                          "type": "boolean"
                        },
                        "spec": {
                          "description": "A list of items, or (for an array) one item.",
                          "oneOf": [
                            {
                              "type": "array",
                              "items": {
                                "type": "object"
                              }
                            },
                            {
                              "type": "object"
                            }
                          ]
                        }
                      }
                    },
                    "description": "The fields, checked before they are stored."
                  },
                  "strict": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The data structure as changed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "An organization's data structure: the fields a JSON module, a data store or a webhook reads.",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "strict": {
                          "type": "boolean",
                          "description": "Strict: a payload with a field the structure does not define is refused (at a webhook)."
                        },
                        "created_by": {
                          "type": "string",
                          "nullable": true
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "spec": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "description": "One field of a data structure, in Make's parameter shape. `type` is one of text, number, boolean, date, buffer, collection, array. `spec`: a collection's fields (a list of items), or an array's item (one item, its `name` optional, or a list of fields for items that are collections); absent, any fields or items. Unknown properties are refused; a property set to null is left out.",
                            "required": [
                              "name",
                              "type"
                            ],
                            "properties": {
                              "name": {
                                "type": "string",
                                "description": "Unique on its level; at most 256 characters; not __proto__, constructor or prototype."
                              },
                              "type": {
                                "type": "string",
                                "enum": [
                                  "text",
                                  "number",
                                  "boolean",
                                  "date",
                                  "buffer",
                                  "collection",
                                  "array"
                                ]
                              },
                              "label": {
                                "type": "string"
                              },
                              "help": {
                                "type": "string"
                              },
                              "required": {
                                "type": "boolean"
                              },
                              "default": {
                                "description": "Any JSON value, nested at most 64 levels."
                              },
                              "multiline": {
                                "type": "boolean"
                              },
                              "spec": {
                                "description": "A list of items, or (for an array) one item.",
                                "oneOf": [
                                  {
                                    "type": "array",
                                    "items": {
                                      "type": "object"
                                    }
                                  },
                                  {
                                    "type": "object"
                                  }
                                ]
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: `nothing_to_change`, `invalid_name`, `invalid_spec` (with `errors`) or `invalid_strict`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    },
                    "errors": {
                      "type": "array",
                      "description": "`invalid_spec` only: each problem, with its path from `spec` (at most 50).",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "description": "The items are over 1,900,000 bytes as JSON (`data_structure_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteDataStructure",
        "tags": [
          "Flows"
        ],
        "summary": "Delete a data structure",
        "description": "Deletes a data structure nothing uses. One that a flow (its draft or the version it runs, the trash included) or a data store uses answers 409 `data_structure_in_use`, naming them. Owners and admins only; recorded in the audit log with its id and name.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "In use (`data_structure_in_use`): the flows and data stores that use it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    },
                    "flows": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "trashed": {
                            "type": "boolean"
                          }
                        }
                      }
                    },
                    "data_stores": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/flow-apps": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "getFlowAppAccess",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Which early-release flow apps an organisation sees",
        "description": "The platform owner only, in a browser session (an API token is refused with `session_required`). The organisation's alpha flag (it sees every `alpha` app pack) and the `beta` app packs it was enabled for. A `ga` app pack needs neither: every organisation with the flow engine sees it; an `off` pack nobody does.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The access.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowAppAccess"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not the platform owner (`owner_only`), or an API token (`session_required`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/flow-apps/alpha": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "put": {
        "operationId": "setFlowAppsAlpha",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Let an organisation see the alpha flow apps, or stop it",
        "description": "The platform owner only, in a browser session. Sets the organisation's alpha flag and nothing else. A change is written to the organisation's audit log (`resource_type` `flow`, `event_type` `apps_alpha_changed`, with who, from where and when) together with the change; a request that changes nothing writes nothing (`changed: false`). Once off, a flow that uses an alpha app pack can no longer be saved while on, turned on, run or retried, and a run already going is refused the next time it uses that app's connection.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetFlowAppsAlphaInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Set.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowAppsAlphaChange"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not the platform owner (`owner_only`), an API token (`session_required`), or a request without an `Origin` header naming the app's own origin (`cross_origin`: a change is accepted from the app itself only).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/flow-apps/{appId}/access": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        },
        {
          "name": "appId",
          "in": "path",
          "required": true,
          "description": "The app pack's id (lower-case letters, digits and inner hyphens).",
          "schema": {
            "type": "string",
            "maxLength": 100
          }
        }
      ],
      "put": {
        "operationId": "grantFlowAppAccess",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Enable an organisation for a beta flow app",
        "description": "The platform owner only, in a browser session. The organisation then sees the app pack while its status is `beta` (a `ga` pack it sees anyway; an `off` one it never does). Records who enabled it and when. A change is written to the organisation's audit log (`event_type` `app_access_granted`) together with the change; enabling it again changes nothing (`changed: false`). An app id that is no app pack SignFlow offers is a 404.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Enabled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowAppAccessChange"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not the platform owner (`owner_only`), an API token (`session_required`), or a request without an `Origin` header naming the app's own origin (`cross_origin`: a change is accepted from the app itself only).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "revokeFlowAppAccess",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Take a beta flow app from an organisation",
        "description": "The platform owner only, in a browser session. Any app id is accepted, so the access to an app pack SignFlow no longer offers can be removed too. A change is written to the organisation's audit log (`event_type` `app_access_revoked`) together with the change; revoking access it does not have changes nothing (`changed: false`). The organisation's connections of that app stay: they still list and can be removed, but cannot be used, rotated or connected again while it cannot see the app.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowAppAccessChange"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not the platform owner (`owner_only`), an API token (`session_required`), or a request without an `Origin` header naming the app's own origin (`cross_origin`: a change is accepted from the app itself only).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/flow-engine": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "put": {
        "operationId": "setFlowEngine",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Turn the flow engine on or off for an organisation",
        "description": "The platform owner only, in a browser session (an API token is refused with `session_required`). Sets the organisation's flow engine flag and nothing else: its flows, their versions and whether each is on stay as they are. While it is off, the flows API answers 402 `feature_locked` for the organisation, none of its flows is started on a schedule and its flow webhooks are not found. A change is written to the organisation's audit log (`resource_type` `flow`, `event_type` `flow_engine_changed`, with `from` and `to`, who, from where and when) together with the change; a request that changes nothing writes nothing (`changed: false`). The organisation also needs Connect for the flow engine to be reachable. The Features endpoint never sets this flag.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetFlowEngineInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Set.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FlowEngineChange"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not the platform owner (`owner_only`), an API token (`session_required`), or a request without an `Origin` header naming the app's own origin (`cross_origin`: a change is accepted from the app itself only).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/admin/oauth-clients": {
      "get": {
        "operationId": "listOAuthClients",
        "tags": [
          "Platform Admin"
        ],
        "summary": "The OAuth clients of the apps that connect with LeonAI's OAuth client",
        "description": "The platform owner only, in a browser session (an API token, `admin` included, is refused with `session_required`). Every app pack whose OAuth 2 connection uses the OAuth client LeonAI registered with the provider (its common data names `clientId` and `clientSecret`), whether people connect it by signing in at the provider's own page (an `authorize` step) or with the client alone and a value of their own (client credentials), whatever its release status, so a client can be set before the app is released, with the OAuth client id set for it (or null) and whether a client secret is set. The secret itself is never returned, nor any part of it. A client left for an app pack that no longer takes one, or is no longer offered, is listed too, with `available: false`, so it can be removed.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The apps and their clients.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OAuthClientEntry"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not the platform owner (`owner_only`), or an API token (`session_required`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/admin/oauth-clients/{packId}": {
      "parameters": [
        {
          "name": "packId",
          "in": "path",
          "required": true,
          "description": "The app pack's id (lower-case letters, digits and inner hyphens).",
          "schema": {
            "type": "string",
            "maxLength": 100
          }
        }
      ],
      "put": {
        "operationId": "setOAuthClient",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Set or rotate an app's OAuth client",
        "description": "The platform owner only, in a browser session. Sets the OAuth client (id and secret) LeonAI registered with the provider for this app pack, or replaces the one set (`action: rotated`). The secret is stored encrypted, the way connection credentials are, and is never returned: the answer only says it is set. Each change is written to the OAuth client audit trail (the app, the client id, created or rotated, who, from where and when; never the secret) together with the change. An app id that is no app pack SignFlow offers is a 404; an app pack whose connection does not use LeonAI's OAuth client takes none (400).",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetOAuthClientInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Set.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OAuthClientChange"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not the platform owner (`owner_only`), or an API token (`session_required`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "deleteOAuthClient",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Remove an app's OAuth client",
        "description": "The platform owner only, in a browser session. Removes the OAuth client set for this app pack. Any app id is accepted, so the client of an app pack SignFlow no longer offers can be removed too. The removal is written to the OAuth client audit trail (the app, the client id removed, who, from where and when) together with the change; removing a client that is not set changes nothing (`changed: false`).",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OAuthClientDeletion"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not the platform owner (`owner_only`), or an API token (`session_required`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/data-stores": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listDataStores",
        "tags": [
          "Flows"
        ],
        "summary": "List the organization's data stores",
        "description": "By name, with each one’s size and what its records hold. Any member of an organization with the flow engine.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 200; default 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of data stores.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "data_structure_id": {
                            "type": "string",
                            "nullable": true,
                            "description": "The data structure its records follow; null for none."
                          },
                          "max_size_mb": {
                            "type": "number",
                            "description": "Its size in MB (1 MB = 1,024 KiB): its records never hold more."
                          },
                          "max_bytes": {
                            "type": "integer",
                            "description": "Its size in bytes."
                          },
                          "size_bytes": {
                            "type": "integer",
                            "description": "What its records hold: each record’s key and data as JSON, in UTF-8 bytes."
                          },
                          "record_count": {
                            "type": "integer"
                          },
                          "created_by": {
                            "type": "string",
                            "nullable": true
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "createDataStore",
        "tags": [
          "Flows"
        ],
        "summary": "Create a data store",
        "description": "Creates an empty data store from its name, its size in MB and, optionally, the data structure its records follow. At most 1,000 an organization. Owners and admins only; recorded in the audit log with its id and name.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "max_size_mb"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Trimmed; 1 to 200 characters."
                  },
                  "data_structure_id": {
                    "type": "string",
                    "nullable": true,
                    "description": "A data structure of the organization; null for none."
                  },
                  "max_size_mb": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "The store’s size in MB (1 MB = 1,024 KiB), from 1."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "data_structure_id": {
                          "type": "string",
                          "nullable": true,
                          "description": "The data structure its records follow; null for none."
                        },
                        "max_size_mb": {
                          "type": "number",
                          "description": "Its size in MB (1 MB = 1,024 KiB): its records never hold more."
                        },
                        "max_bytes": {
                          "type": "integer",
                          "description": "Its size in bytes."
                        },
                        "size_bytes": {
                          "type": "integer",
                          "description": "What its records hold: each record’s key and data as JSON, in UTF-8 bytes."
                        },
                        "record_count": {
                          "type": "integer"
                        },
                        "created_by": {
                          "type": "string",
                          "nullable": true
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: `invalid_name`, `invalid_size`, `invalid_data_structure_id`, or `data_structure_not_found` (not a data structure of the organization).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "The flow engine is not enabled for the organization (`feature_locked`), or the organization’s data stores would take more than its data store quota (`data_store_quota_exceeded`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "The organization has 1,000 data stores (`data_store_limit`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/data-stores/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getDataStore",
        "tags": [
          "Flows"
        ],
        "summary": "Read a data store",
        "description": "Its size and what its records hold (`record_count`, `size_bytes`). Another organization’s store answers `data_store_not_found`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The data store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "data_structure_id": {
                          "type": "string",
                          "nullable": true,
                          "description": "The data structure its records follow; null for none."
                        },
                        "max_size_mb": {
                          "type": "number",
                          "description": "Its size in MB (1 MB = 1,024 KiB): its records never hold more."
                        },
                        "max_bytes": {
                          "type": "integer",
                          "description": "Its size in bytes."
                        },
                        "size_bytes": {
                          "type": "integer",
                          "description": "What its records hold: each record’s key and data as JSON, in UTF-8 bytes."
                        },
                        "record_count": {
                          "type": "integer"
                        },
                        "created_by": {
                          "type": "string",
                          "nullable": true
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateDataStore",
        "tags": [
          "Flows"
        ],
        "summary": "Change a data store",
        "description": "Sets its name, its data structure (null for none) or its size, whichever is given; its records stay as they are. Owners and admins only; recorded in the audit log with its id, name and which parts changed.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Trimmed; 1 to 200 characters."
                  },
                  "data_structure_id": {
                    "type": "string",
                    "nullable": true,
                    "description": "A data structure of the organization; null for none."
                  },
                  "max_size_mb": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "The store’s size in MB (1 MB = 1,024 KiB), from 1."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The data store as changed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "data_structure_id": {
                          "type": "string",
                          "nullable": true,
                          "description": "The data structure its records follow; null for none."
                        },
                        "max_size_mb": {
                          "type": "number",
                          "description": "Its size in MB (1 MB = 1,024 KiB): its records never hold more."
                        },
                        "max_bytes": {
                          "type": "integer",
                          "description": "Its size in bytes."
                        },
                        "size_bytes": {
                          "type": "integer",
                          "description": "What its records hold: each record’s key and data as JSON, in UTF-8 bytes."
                        },
                        "record_count": {
                          "type": "integer"
                        },
                        "created_by": {
                          "type": "string",
                          "nullable": true
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: `nothing_to_change`, `invalid_name`, `invalid_size`, `invalid_data_structure_id` or `data_structure_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "The flow engine is not enabled for the organization (`feature_locked`), or the organization’s data stores would take more than its data store quota (`data_store_quota_exceeded`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The size is below what its records hold (`data_store_too_small`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteDataStore",
        "tags": [
          "Flows"
        ],
        "summary": "Delete a data store",
        "description": "Deletes the data store and all its records. One that a flow uses (its draft or the version it runs, the trash included) answers 409 `data_store_in_use`, naming them. Owners and admins only; recorded in the audit log with its id, name and how many records it held.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "In use (`data_store_in_use`): the flows that use it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    },
                    "flows": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "trashed": {
                            "type": "boolean"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/data-stores/{id}/records": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listDataStoreRecords",
        "tags": [
          "Flows"
        ],
        "summary": "Browse a data store's records",
        "description": "A page of records by key. A page holds at most `limit` records and about 4 MB of them (always one); `next` is the key to pass as `after` for the next page, null on the last. Needs `flows.edit`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "parameters": [
          {
            "name": "after",
            "in": "query",
            "required": false,
            "description": "The `next` of the page before; absent for the first page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 100; default 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of records.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string"
                          },
                          "data": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "The record’s fields."
                          },
                          "size_bytes": {
                            "type": "integer",
                            "description": "Its key and its data as JSON, in UTF-8 bytes."
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "next": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "addDataStoreRecord",
        "tags": [
          "Flows"
        ],
        "summary": "Add a record",
        "description": "Adds a record under a key the store does not hold (409 `record_exists` otherwise). Without a key, or with an empty one, the key is generated: 12 lowercase hex characters. Needs `flows.edit`; recorded in the audit log with the store and the key, never the data.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "key": {
                    "type": "string",
                    "description": "1 to 1,024 bytes, kept exactly; absent or empty: generated."
                  },
                  "data": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "The record’s fields, a JSON object of at most 1,900,000 bytes. With a data structure, each field is checked as Make checks parameters: coerced to its type (text \"5\" to the number 5), a required one present, and only the structure’s fields kept (a strict structure refuses any other)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Added.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "data": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "The record’s fields."
                        },
                        "size_bytes": {
                          "type": "integer",
                          "description": "Its key and its data as JSON, in UTF-8 bytes."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: `invalid_key` (1 to 1,024 bytes of text), `invalid_data` (not a JSON object) or `invalid_record` (it does not fit the data structure: `error` is \"Validation failed for N parameter(s).\", `problems` names each in Make’s words).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    },
                    "problems": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`record_exists` (adding a key the store holds), `data_store_full` (the record would take the store past its size) or `data_structure_invalid` (its data structure no longer reads as valid).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The record’s data is over 1,900,000 bytes as JSON (`record_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteAllDataStoreRecords",
        "tags": [
          "Flows"
        ],
        "summary": "Delete all of a data store's records",
        "description": "Deletes every record; the store stays, empty. Needs `flows.edit`; recorded in the audit log with how many records went.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "deleted": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/data-stores/{id}/records/delete": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "deleteDataStoreRecords",
        "tags": [
          "Flows"
        ],
        "summary": "Delete records by key",
        "description": "Deletes the records of up to 100 keys; a key the store does not hold is skipped. Each key is 1 to 1,024 bytes of UTF-8 text; a longer one refuses the whole request (400 `invalid_keys`) and deletes nothing. Needs `flows.edit`; recorded in the audit log with the keys it deleted and how many records went, over several entries (each naming its part) when the keys do not fit one.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "keys"
                ],
                "properties": {
                  "keys": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "description": "A record’s key: 1 to 1,024 bytes of UTF-8 text."
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "deleted": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: `invalid_keys`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/data-stores/{id}/records/{key}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "key",
          "in": "path",
          "required": true,
          "description": "The record’s key, URL-encoded (it may hold any text, `/` included).",
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getDataStoreRecord",
        "tags": [
          "Flows"
        ],
        "summary": "Read a record",
        "description": "The record of the key; `record_not_found` when the store holds none. Needs `flows.edit`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The record.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "data": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "The record’s fields."
                        },
                        "size_bytes": {
                          "type": "integer",
                          "description": "Its key and its data as JSON, in UTF-8 bytes."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "replaceDataStoreRecord",
        "tags": [
          "Flows"
        ],
        "summary": "Add or replace a record",
        "description": "Replaces the record’s data whole, or adds the record when the store does not hold the key. Needs `flows.edit`; recorded in the audit log with the store and the key, never the data.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "data"
                ],
                "properties": {
                  "data": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "The record’s fields, a JSON object of at most 1,900,000 bytes. With a data structure, each field is checked as Make checks parameters: coerced to its type (text \"5\" to the number 5), a required one present, and only the structure’s fields kept (a strict structure refuses any other)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The record as written.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "data": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "The record’s fields."
                        },
                        "size_bytes": {
                          "type": "integer",
                          "description": "Its key and its data as JSON, in UTF-8 bytes."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: `invalid_key` (1 to 1,024 bytes of text), `invalid_data` (not a JSON object) or `invalid_record` (it does not fit the data structure: `error` is \"Validation failed for N parameter(s).\", `problems` names each in Make’s words).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    },
                    "problems": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`record_exists` (adding a key the store holds), `data_store_full` (the record would take the store past its size) or `data_structure_invalid` (its data structure no longer reads as valid).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The record’s data is over 1,900,000 bytes as JSON (`record_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteDataStoreRecord",
        "tags": [
          "Flows"
        ],
        "summary": "Delete a record",
        "description": "Deletes the record of the key; `record_not_found` when the store holds none. Needs `flows.edit`; recorded in the audit log with the store and the key.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/custom-variables": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "listCustomVariables",
        "tags": [
          "Flows"
        ],
        "summary": "List the organization's custom variables, or only their names and types",
        "description": "Every custom variable of the organization (at most 200), by name, with its value. Owners and admins only (custom variables are owner/admin only, reads included).\n\nWith `view=names`, only each variable's `name` and `type`, by name, never its value: for anyone who builds flows (`flows.edit`), so the flow builder can offer `{{var.custom.<name>}}` in a mapping. A member without `flows.edit` gets 403 naming it. Any other `view` is a 400 `invalid_view`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The custom variables; with `view=names`, each one's name and type only.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "oneOf": [
                          {
                            "type": "object",
                            "description": "One of the organization's custom variables: a value every flow of the organization reads (`var.custom.<name>`), read once when a run starts.",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string",
                                "description": "Letters, digits, $ and _, not starting with a digit; at most 128 characters. Never changes."
                              },
                              "type": {
                                "type": "string",
                                "enum": [
                                  "text",
                                  "number",
                                  "boolean",
                                  "date"
                                ]
                              },
                              "value": {
                                "description": "The value, as its type takes it: text a non-empty string, a number a number, a boolean true or false, a date ISO 8601 text (`2026-01-31`, midnight UTC, or a date and time with its zone, `2026-01-31T10:00:00Z`; answered as its ISO instant). At most 65,536 bytes in UTF-8. Stored as given: not for secrets.",
                                "oneOf": [
                                  {
                                    "type": "string"
                                  },
                                  {
                                    "type": "number"
                                  },
                                  {
                                    "type": "boolean"
                                  }
                                ]
                              },
                              "created_by": {
                                "type": "string",
                                "nullable": true
                              },
                              "created_at": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "updated_by": {
                                "type": "string",
                                "nullable": true
                              },
                              "updated_at": {
                                "type": "string",
                                "format": "date-time"
                              }
                            }
                          },
                          {
                            "type": "object",
                            "description": "With `view=names`: a variable's name and type, never its value.",
                            "required": [
                              "name",
                              "type"
                            ],
                            "additionalProperties": false,
                            "properties": {
                              "name": {
                                "type": "string",
                                "description": "The variable's name, as `{{var.custom.<name>}}` reads it."
                              },
                              "type": {
                                "type": "string",
                                "enum": [
                                  "text",
                                  "number",
                                  "boolean",
                                  "date"
                                ]
                              }
                            }
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "parameters": [
          {
            "name": "view",
            "in": "query",
            "required": false,
            "description": "`names`: only each variable's name and type (needs `flows.edit`, not owner or admin). Left out: every variable with its value (owners and admins).",
            "schema": {
              "type": "string",
              "enum": [
                "names"
              ]
            }
          }
        ]
      },
      "post": {
        "operationId": "createCustomVariable",
        "tags": [
          "Flows"
        ],
        "summary": "Create a custom variable",
        "description": "Creates a custom variable from its name, type and value. A run reads the values it starts with: a change takes effect from the next run. Owners and admins only (custom variables are owner/admin only, reads included). Recorded in the audit log with its id, name and type, never its value.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "type",
                  "value"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Letters, digits, $ and _, not starting with a digit; at most 128 characters."
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "text",
                      "number",
                      "boolean",
                      "date"
                    ]
                  },
                  "value": {
                    "description": "The value, as its type takes it: text a non-empty string, a number a number, a boolean true or false, a date ISO 8601 text (`2026-01-31`, midnight UTC, or a date and time with its zone, `2026-01-31T10:00:00Z`; answered as its ISO instant). At most 65,536 bytes in UTF-8. Stored as given: not for secrets.",
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      },
                      {
                        "type": "boolean"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "One of the organization's custom variables: a value every flow of the organization reads (`var.custom.<name>`), read once when a run starts.",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string",
                          "description": "Letters, digits, $ and _, not starting with a digit; at most 128 characters. Never changes."
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "text",
                            "number",
                            "boolean",
                            "date"
                          ]
                        },
                        "value": {
                          "description": "The value, as its type takes it: text a non-empty string, a number a number, a boolean true or false, a date ISO 8601 text (`2026-01-31`, midnight UTC, or a date and time with its zone, `2026-01-31T10:00:00Z`; answered as its ISO instant). At most 65,536 bytes in UTF-8. Stored as given: not for secrets.",
                          "oneOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        },
                        "created_by": {
                          "type": "string",
                          "nullable": true
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_by": {
                          "type": "string",
                          "nullable": true
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A bad name (`invalid_name`), type (`invalid_type`) or value (`invalid_value`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "The organization has a variable of this name (`custom_variable_exists`), or has 200 (`custom_variable_limit`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The value is over 65,536 bytes (`value_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/custom-variables/{name}": {
      "parameters": [
        {
          "name": "name",
          "in": "path",
          "required": true,
          "description": "The variable's name (percent-encode a `$`).",
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getCustomVariable",
        "tags": [
          "Flows"
        ],
        "summary": "Read a custom variable",
        "description": "Owners and admins only (custom variables are owner/admin only, reads included). Another organization's variable is a 404.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The custom variable.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "One of the organization's custom variables: a value every flow of the organization reads (`var.custom.<name>`), read once when a run starts.",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string",
                          "description": "Letters, digits, $ and _, not starting with a digit; at most 128 characters. Never changes."
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "text",
                            "number",
                            "boolean",
                            "date"
                          ]
                        },
                        "value": {
                          "description": "The value, as its type takes it: text a non-empty string, a number a number, a boolean true or false, a date ISO 8601 text (`2026-01-31`, midnight UTC, or a date and time with its zone, `2026-01-31T10:00:00Z`; answered as its ISO instant). At most 65,536 bytes in UTF-8. Stored as given: not for secrets.",
                          "oneOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        },
                        "created_by": {
                          "type": "string",
                          "nullable": true
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_by": {
                          "type": "string",
                          "nullable": true
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateCustomVariable",
        "tags": [
          "Flows"
        ],
        "summary": "Change a custom variable",
        "description": "Changes the variable's value, and its type when given (its current type otherwise). A variable is never renamed: a different `name` is refused (`cannot_rename`). The value it takes joins its history. Runs going on keep the value they started with. Owners and admins only (custom variables are owner/admin only, reads included). Recorded in the audit log with its id, name and type, never its value.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "value"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "text",
                      "number",
                      "boolean",
                      "date"
                    ]
                  },
                  "value": {
                    "description": "The value, as its type takes it: text a non-empty string, a number a number, a boolean true or false, a date ISO 8601 text (`2026-01-31`, midnight UTC, or a date and time with its zone, `2026-01-31T10:00:00Z`; answered as its ISO instant). At most 65,536 bytes in UTF-8. Stored as given: not for secrets.",
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      },
                      {
                        "type": "boolean"
                      }
                    ]
                  },
                  "name": {
                    "type": "string",
                    "description": "Optional; when given, the variable’s own name."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Changed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "One of the organization's custom variables: a value every flow of the organization reads (`var.custom.<name>`), read once when a run starts.",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string",
                          "description": "Letters, digits, $ and _, not starting with a digit; at most 128 characters. Never changes."
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "text",
                            "number",
                            "boolean",
                            "date"
                          ]
                        },
                        "value": {
                          "description": "The value, as its type takes it: text a non-empty string, a number a number, a boolean true or false, a date ISO 8601 text (`2026-01-31`, midnight UTC, or a date and time with its zone, `2026-01-31T10:00:00Z`; answered as its ISO instant). At most 65,536 bytes in UTF-8. Stored as given: not for secrets.",
                          "oneOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        },
                        "created_by": {
                          "type": "string",
                          "nullable": true
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_by": {
                          "type": "string",
                          "nullable": true
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A rename (`cannot_rename`), a bad type (`invalid_type`) or value (`invalid_value`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "description": "The value is over 65,536 bytes (`value_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteCustomVariable",
        "tags": [
          "Flows"
        ],
        "summary": "Delete a custom variable",
        "description": "Deletes the variable and its history. A flow that reads it then reads it empty. Owners and admins only (custom variables are owner/admin only, reads included). Recorded in the audit log with its id and name.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:write"
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/custom-variables/{name}/history": {
      "parameters": [
        {
          "name": "name",
          "in": "path",
          "required": true,
          "description": "The variable's name (percent-encode a `$`).",
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "get": {
        "operationId": "getCustomVariableHistory",
        "tags": [
          "Flows"
        ],
        "summary": "A custom variable's history",
        "description": "The values the variable has had, newest first (its current one included; at most 20), by whom and when. Owners and admins only (custom variables are owner/admin only, reads included).",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:read"
        ],
        "responses": {
          "200": {
            "description": "The history, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "enum": [
                              "text",
                              "number",
                              "boolean",
                              "date"
                            ]
                          },
                          "value": {
                            "description": "The value, as its type takes it: text a non-empty string, a number a number, a boolean true or false, a date ISO 8601 text (`2026-01-31`, midnight UTC, or a date and time with its zone, `2026-01-31T10:00:00Z`; answered as its ISO instant). At most 65,536 bytes in UTF-8. Stored as given: not for secrets.",
                            "oneOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "number"
                              },
                              {
                                "type": "boolean"
                              }
                            ]
                          },
                          "changed_by": {
                            "type": "string",
                            "nullable": true
                          },
                          "changed_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/flows/{id}/run": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "$ref": "#/components/parameters/OrganizationIdQuery"
        }
      ],
      "post": {
        "operationId": "runFlowViaApi",
        "tags": [
          "Flows"
        ],
        "summary": "Run a flow via API",
        "description": "Starts a real run of an active flow's saved version (never its draft), as Make's run endpoint does without `responsive`, and answers its `executionId` as soon as the run exists; the run goes on in the background: read it with `GET /flows/{id}/runs/{runId}` (`trigger_kind` `api`), or give a `callbackUrl`, which is POSTed once when the run ends. Its start runs as a real Run once of it does: a flow that starts with *Start flow* takes `data` as its inputs; a trigger that polls is checked first, and what the check finds starts the run (a check that finds nothing new is recorded as a check run, ended with Success, and a check that fails as a run ended with Error; either's id is answered); a flow that starts with a custom webhook takes up to the module's maximum of the flow's waiting requests, oldest first, and answers 409 (`queue_empty`) when none waits, (`runs_immediately`) when its schedule is Immediately, or (`invalid_schedule`) when its stored schedule is not valid. A saved flow with problems (a connection deleted since, a called flow turned off) is refused with 409 (`invalid_graph`, with `issues`).\n\nWho may run it: an API token with the `flows:run` scope (`admin` holds every scope) whose user holds the `flows.activate` permission; a token with the scope whose user lacks the permission is refused, and so is the reverse. A signed-in session (JWT) passes the scope check, as on every flows route, and still needs `flows.activate`. The run is that user's: the documents it sends are sent as the token's user, and a user no longer a member of the organization sends none. The run is recorded in the audit log (`run_started`) with the token's id (never the token) and its user, and no input value. A run via API never counts toward the flow's errors in a row (its maximum number of errors) and never turns the flow off.\n\nRate limit: 100 calls in 60 seconds per API token (a session: per user), then 429 with `Retry-After`. Make exempts its run endpoint from the organization's rate limits; SignFlow keeps this per-token limit.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-required-scopes": [
          "flows:run"
        ],
        "x-rate-limit": {
          "requests": 100,
          "seconds": 60
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Any other key is ignored.",
                "properties": {
                  "data": {
                    "type": "object",
                    "description": "The flow's inputs by input name, as its interface defines them: checked and coerced to each input's type before the run starts (an input not given takes its default), with the messages Run once gives.",
                    "additionalProperties": true,
                    "example": {
                      "email": "dana@example.com",
                      "amount": "100"
                    }
                  },
                  "responsive": {
                    "type": "boolean",
                    "default": false,
                    "description": "Only `false` for now: waiting for the run's outputs (`true`) is not available yet and answers 400 (`responsive_unavailable`)."
                  },
                  "callbackUrl": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 8000,
                    "format": "uri",
                    "description": "An address SignFlow POSTs once when the run ends, whatever its status (Success, Warning, Error, Stopped, or a failure of the platform), with the JSON body `{ \"executionId\": \"<the run's id>\", \"statusUrl\": \"<the run's URL in this API: GET it with a token that has flows:read>\" }` and `Content-Type: application/json`, as Make's run endpoint does. One attempt, never retried: an answer of any status, or none within 15 seconds, is recorded on the run (`callback` in `GET /flows/{id}/runs/{runId}`). It is checked before anything starts, as the HTTP app checks an address: `https` only, no user name or password in it, no IP address, never `localhost`, a metadata service or one of LeonAI's own hosts, at most 8,000 characters; otherwise 400 (`callback_invalid`) and no run starts. Redirects are followed (at most 10), each checked the same way. Each POST and each redirect counts toward the organization's outbound limit of 1,000 requests per 60 seconds. The address is kept only with the run's own data and deleted once it was tried: it is never shown, logged or audited, so it may carry a token. `null` is none. A run that could not start sends none."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The run started; or, for a flow whose trigger polls, its check was recorded: a check that found nothing new as a check run ended with Success, a check that failed as a run ended with Error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "executionId"
                  ],
                  "properties": {
                    "executionId": {
                      "type": "string",
                      "description": "The run's id."
                    }
                  }
                },
                "example": {
                  "executionId": "run_V1StGXR8_Z5jdHi6B-myT"
                }
              }
            }
          },
          "400": {
            "description": "The flow is off (`not_active`, \"Scenario is not active.\"); inputs that are not valid (`invalid_inputs`, `type` `InvalidConfigurationError`, with its `problems`; `data` that is not an object); `responsive: true` (`responsive_unavailable`); a `callbackUrl` that is not text, is longer than 8,000 characters or is not an address SignFlow sends to (`callback_invalid`, saying why without quoting it); or a malformed request (such as a body that is not a JSON object, a `responsive` that is not true or false, or a session call without `organization_id`).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "InvalidConfigurationError"
                          ],
                          "description": "`invalid_inputs` for values the inputs refused: Make's class for them."
                        },
                        "problems": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "`invalid_inputs` for values the inputs refused: Make's message for each problem, in the interface's order, then the keys it does not have."
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "error": "Bad Request. Validation failed for 1 parameter(s). Missing value of required parameter 'email'.",
                  "code": "invalid_inputs",
                  "type": "InvalidConfigurationError",
                  "problems": [
                    "Missing value of required parameter 'email'."
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The run cannot start now: a flow that starts with a custom webhook with no request waiting (`queue_empty`), on Immediately (`runs_immediately`), or whose stored schedule is not valid (`invalid_schedule`); another run holds the trigger's records or the webhook's requests (`trigger_busy`); the saved flow has problems (`invalid_graph`, with `issues`); or the flow was never saved (`not_published`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The request body is too large (`graph_too_large`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "description": "Runs are not available here (`runs_unavailable`), or the run could not be started (`run_not_started`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/signing/{submissionId}/link-preview": {
      "parameters": [
        {
          "name": "submissionId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getSigningLinkPreview",
        "tags": [
          "Signing Ceremony"
        ],
        "summary": "Open Graph preview for a signing link",
        "description": "Unauthenticated, and deliberately thin: what a chat app shows when someone pastes a signing link. It carries the organisation's preview title, description and logo — never the document's contents, since anyone who sees the link can fetch this.",
        "security": [],
        "x-rate-limit": {
          "requests": 120,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The preview.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "description": {
                      "type": "string"
                    },
                    "image": {
                      "type": "string",
                      "nullable": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/signing/preview/{submissionId}": {
      "parameters": [
        {
          "name": "submissionId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "previewSubmission",
        "tags": [
          "Signing Ceremony"
        ],
        "summary": "Preview a held request",
        "description": "Unauthenticated; the token is the credential. Returns the document's name, status, page count and field boxes (percentages of the page). Read-only: never a signing session, never marks anything viewed. A wrong token, a request that was never held, and one that has ended (completed, voided, or released and past its expiry) all answer the same 404. A held request stays previewable until it is released or voided. The token is sent in the request body, never in the URL, so it does not reach request logs.",
        "security": [],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "The preview token: the part after `#t=` in the held request's `preview_url`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The preview.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "status": {
                          "$ref": "#/components/schemas/SubmissionStatus"
                        },
                        "page_count": {
                          "type": "integer"
                        },
                        "fields": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "field_key": {
                                "type": "string"
                              },
                              "field_type": {
                                "type": "string"
                              },
                              "label": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "required": {
                                "type": "integer"
                              },
                              "assigned_signer": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "page": {
                                "type": "integer"
                              },
                              "x": {
                                "type": "number"
                              },
                              "y": {
                                "type": "number"
                              },
                              "width": {
                                "type": "number"
                              },
                              "height": {
                                "type": "number"
                              },
                              "repeat_on_all_pages": {
                                "type": "boolean",
                                "description": "The signer sees this field on every page, at the same position."
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signing/preview/{submissionId}/document": {
      "parameters": [
        {
          "name": "submissionId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "downloadSubmissionPreview",
        "tags": [
          "Signing Ceremony"
        ],
        "summary": "Download a held request's document",
        "description": "The stored PDF exactly as the signer will get it, as an attachment. Each download is recorded on the organisation's audit log. Same 404 rules as the preview. The token is sent in the request body, never in the URL, so it does not reach request logs.",
        "security": [],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "The preview token: the part after `#t=` in the held request's `preview_url`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The PDF.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signing/init-session": {
      "post": {
        "operationId": "initSigningSession",
        "tags": [
          "Signing Ceremony"
        ],
        "summary": "Open a signing session",
        "description": "The first call the ceremony makes. Identifies the signer — by their token, or by email for a link that carries none — and starts a session cookie for everything that follows.\n\nThe refusals are as meaningful as the success: `410` with a `status` says the document is finished, declined or expired, and `403` means it is not this signer's turn yet in a sequential flow. Show those to the signer rather than treating them as errors.",
        "security": [],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "submission_id"
                ],
                "properties": {
                  "submission_id": {
                    "type": "string"
                  },
                  "token": {
                    "type": "string",
                    "description": "The signer's token from their invitation link."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Identifies the signer where the link carries no token."
                  },
                  "signer_name": {
                    "type": "string",
                    "description": "For an open link, the name the anonymous signer gives."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Session open. Carries the signer, the document and its fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SigningSession"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "description": "Not this signer's turn yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "description": "Finished, declined or expired. `status` says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SigningGoneError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signing/session": {
      "post": {
        "operationId": "getSigningSession",
        "tags": [
          "Signing Ceremony"
        ],
        "summary": "Load the signing session",
        "description": "Returns the signer, the document and the fields to fill. If the document is behind a password or an identity check, this answers `403` with `code: password_required` or `otp_required` — clear the gate first, then call again.",
        "security": [
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "submission_id"
                ],
                "properties": {
                  "submission_id": {
                    "type": "string"
                  },
                  "token": {
                    "type": "string"
                  },
                  "signer_name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SigningSession"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "description": "A gate must be cleared first — `code` is `password_required` or `otp_required`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "description": "Finished, declined or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SigningGoneError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signing/init": {
      "post": {
        "operationId": "initSigningLegacy",
        "tags": [
          "Signing Ceremony"
        ],
        "summary": "Open a signing session (legacy)",
        "description": "The pre-session-cookie entry point, kept for links already in the wild. New integrations should use `POST /signing/init-session`.",
        "security": [],
        "x-rate-limit": {
          "requests": 30,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "submission_id"
                ],
                "properties": {
                  "submission_id": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "signer_name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SigningSession"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "description": "Finished, declined or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SigningGoneError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signing/verify-password": {
      "post": {
        "operationId": "verifySigningPassword",
        "tags": [
          "Signing Ceremony"
        ],
        "summary": "Clear the document password",
        "description": "For documents sent with a password. Attempts are rate-limited and counted; too many and the answer is `429` regardless of whether the next guess would have been right.",
        "security": [
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "password"
                ],
                "properties": {
                  "password": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Correct.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "verified": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "description": "Wrong password, or no signing session.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "verified": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signing/request-otp": {
      "post": {
        "operationId": "requestSigningOtp",
        "tags": [
          "Signing Ceremony"
        ],
        "summary": "Send the signer a one-time code",
        "description": "Emails a code to the address on file. The response gives only a masked hint of where it went — enough for the signer to recognise their own address, not enough to disclose it to whoever else holds the link.",
        "security": [
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sent": {
                      "type": "boolean"
                    },
                    "destination_hint": {
                      "type": "string",
                      "description": "The address, masked."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No contact address on file for this signer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signing/verify-otp": {
      "post": {
        "operationId": "verifySigningOtp",
        "tags": [
          "Signing Ceremony"
        ],
        "summary": "Check the one-time code",
        "security": [
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verified.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "verified": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "description": "Wrong or expired code, or no signing session.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "verified": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signing/document": {
      "get": {
        "operationId": "getSigningDocument",
        "tags": [
          "Signing Ceremony"
        ],
        "summary": "Fetch the PDF to sign",
        "description": "Streams the document for the signer holding the session. Authorised by the session, not by the storage path — so a signer can read the one document they were sent and nothing else.",
        "security": [
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The PDF.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "A password or identity check is still outstanding.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/signing/save-progress": {
      "post": {
        "operationId": "saveSigningProgress",
        "tags": [
          "Signing Ceremony"
        ],
        "summary": "Save a partly filled document",
        "description": "Keeps a signer's work so they can come back to a long form. Uploaded files are spooled to object storage rather than stored inline — a large attachment inlined into the record is what used to make this fail outright.",
        "security": [
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "submission_id",
                  "signer_id",
                  "field_values"
                ],
                "properties": {
                  "submission_id": {
                    "type": "string"
                  },
                  "signer_id": {
                    "type": "string"
                  },
                  "token": {
                    "type": "string"
                  },
                  "field_values": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Keyed by field key."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad body, or an upload over the 10 MB limit — `details` names the offending fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "description": "The submission as a whole is too large to store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "422": {
            "description": "The document is already finished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signing/preflight": {
      "post": {
        "operationId": "preflightSigningSubmit",
        "tags": [
          "Signing Ceremony"
        ],
        "summary": "Would submitting succeed right now?",
        "description": "The read-only twin of `/signing/submit`: the same gates, no writes, no provider calls, no status change. It exists so the ceremony can tell a signer that something is missing **before** they commit, instead of after.\n\n`blockers` is empty when the submission would go through.",
        "security": [
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "submission_id",
                  "action"
                ],
                "properties": {
                  "submission_id": {
                    "type": "string"
                  },
                  "signer_id": {
                    "type": "string"
                  },
                  "token": {
                    "type": "string"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "sign",
                      "decline"
                    ]
                  },
                  "field_values": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "approved": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The verdict.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "blockers": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signing/submit": {
      "post": {
        "operationId": "submitSigning",
        "tags": [
          "Signing Ceremony"
        ],
        "summary": "Sign or decline",
        "description": "The moment the document is committed. `action: \"sign\"` records the signature and field values; `action: \"decline\"` records a refusal with an optional reason, and only where the organisation allows declining at all.\n\nEvery gate is re-checked here — password, identity, payment, read-confirmation — and each refusal carries a `code` you can act on rather than a message you have to parse. Use `POST /signing/preflight` first to find those out without committing.\n\nWhen the last signer finishes, the submission completes and the webhook fires. Note that **the signed PDF does not exist yet at that moment**: it is generated afterwards. An integration that reacts to `submission.completed` by immediately downloading the PDF has to allow for that.",
        "security": [
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "submission_id",
                  "action"
                ],
                "properties": {
                  "submission_id": {
                    "type": "string"
                  },
                  "signer_id": {
                    "type": "string"
                  },
                  "token": {
                    "type": "string"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "sign",
                      "decline"
                    ]
                  },
                  "field_values": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "signature_data": {
                    "type": "string",
                    "description": "The drawn signature, as a PNG data URL."
                  },
                  "decline_reason": {
                    "type": "string"
                  },
                  "approved": {
                    "type": "boolean",
                    "description": "The read-confirmation, where the template requires one."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "signed",
                        "declined"
                      ]
                    },
                    "submission_status": {
                      "$ref": "#/components/schemas/SubmissionStatus"
                    },
                    "redirect_url": {
                      "type": "string",
                      "nullable": true,
                      "description": "Where to send the signer next, when the template sets one."
                    },
                    "success_message": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad body, unknown action, or a required read-confirmation that was not given (`code: read_confirmation_required`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "A gate is unmet — `password_required`, `otp_required`, `payment_required` — or declining is not allowed here.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "410": {
            "description": "The signing link has expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The payload is too large to store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "422": {
            "description": "Already processed, or the submission is no longer active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signing/download-signed-pdf": {
      "post": {
        "operationId": "downloadSignedPdfAsSigner",
        "tags": [
          "Signing Ceremony"
        ],
        "summary": "Download the signed document",
        "description": "For the signer, after signing. Refused with `422` until the document is actually signed.",
        "security": [
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "submission_id"
                ],
                "properties": {
                  "submission_id": {
                    "type": "string"
                  },
                  "signer_id": {
                    "type": "string"
                  },
                  "token": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The signed PDF.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Not signed yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/signing/download-audit-trail": {
      "post": {
        "operationId": "downloadAuditTrailAsSigner",
        "tags": [
          "Signing Ceremony"
        ],
        "summary": "Download the audit trail",
        "description": "The evidence page for the signer's own copy: who signed, when, from where, and the hash chain behind it.",
        "security": [
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "submission_id"
                ],
                "properties": {
                  "submission_id": {
                    "type": "string"
                  },
                  "signer_id": {
                    "type": "string"
                  },
                  "token": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The audit trail PDF.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "Not signed yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/signing/payment/init": {
      "post": {
        "operationId": "initSignerPayment",
        "tags": [
          "Signer Payments"
        ],
        "summary": "Start the signer's payment",
        "description": "Where the template asks the signer for money or a card, this begins it and returns what the ceremony needs to continue — a hosted page to redirect to, or the parameters to tokenise the card in the browser, depending on the provider.\n\nThe three intents differ in what actually happens: a charge takes the money now, a hold reserves it, and a card-on-file saves the card without charging anything.",
        "security": [
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Payment started.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "payment_id": {
                      "type": "string"
                    },
                    "redirect_url": {
                      "type": "string",
                      "description": "Present for providers that use a hosted page."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "This submission does not ask for payment (`code: payment_not_required`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Already paid, no provider configured, or an amount the provider will not take.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "410": {
            "description": "The submission is no longer active, or the link has expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "description": "The provider refused or could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          }
        }
      }
    },
    "/signing/payment/confirm": {
      "post": {
        "operationId": "confirmSignerPayment",
        "tags": [
          "Signer Payments"
        ],
        "summary": "Settle a browser-tokenised payment",
        "description": "For providers that tokenise the card in the browser rather than redirecting: hand back the single-use token and SignFlow settles it server-side.\n\nA declined card is a `200` with `status: \"failed\"` and a `code`, not an HTTP error — the request worked, the payment did not. Reserve your error handling for the 4xx and 5xx cases.",
        "security": [
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "payment_id",
                  "single_use_token"
                ],
                "properties": {
                  "payment_id": {
                    "type": "string"
                  },
                  "single_use_token": {
                    "type": "string",
                    "description": "From the provider's browser tokenisation."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Settled, or declined — read `status`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "description": "`failed` for a decline; otherwise the state the intent reached."
                    },
                    "code": {
                      "type": "string",
                      "description": "Why it failed, when it did."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Already settled, no provider configured, or a provider that settles by callback instead (`code: wrong_flow`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "description": "The provider refused or could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          }
        }
      }
    },
    "/signing/payment/status": {
      "get": {
        "operationId": "getSignerPaymentStatus",
        "tags": [
          "Signer Payments"
        ],
        "summary": "Is the payment gate satisfied?",
        "description": "`satisfied` is the only field that decides whether signing may proceed. It is true once money has been taken or held, **or** once a card has been saved where that is what the template asked for — three different outcomes, one gate.",
        "security": [
          {
            "SigningToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The gate's state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "satisfied": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "string",
                      "description": "`satisfied`, `none`, or the latest attempt's status."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/signing/payment/callback": {
      "post": {
        "operationId": "signerPaymentCallback",
        "tags": [
          "Signer Payments"
        ],
        "summary": "Provider callback",
        "description": "Called by the payment provider, not by you. Answers `200` to anything it accepts for delivery; the result is worked out server-side and reflected in `GET /signing/payment/status`.\n\nThe `nonce` is what ties the callback to the payment it claims to be about.",
        "security": [],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "parameters": [
          {
            "name": "payment_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "nonce",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Received.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "received": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/billing/plans": {
      "get": {
        "operationId": "listPlans",
        "tags": [
          "Billing"
        ],
        "summary": "Plans, add-ons and credit packs",
        "description": "The public price list. Prices are in **agorot** — 1/100 of a shekel — and exclude VAT, as `vatNote` says.\n\nOn a white-label agency's own host, the prices come from that agency's price book rather than SignFlow's, so a reseller's customers see the reseller's prices.\n\n`checkout.companyId` and `checkout.apiPublicKey` are what the browser needs to tokenise a card. Both can be null, and that is not an error: it means checkout is not available here, and the caller should show that rather than render a form that cannot work.",
        "security": [],
        "x-rate-limit": {
          "requests": 60,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The price list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plans": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Plan"
                      }
                    },
                    "packs": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "credits": {
                            "type": "integer"
                          },
                          "priceAgorot": {
                            "type": "integer"
                          }
                        }
                      }
                    },
                    "addons": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "vatNote": {
                      "type": "string",
                      "enum": [
                        "prices_ex_vat"
                      ]
                    },
                    "checkout": {
                      "type": "object",
                      "properties": {
                        "companyId": {
                          "type": "integer",
                          "nullable": true
                        },
                        "apiPublicKey": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "getBillingState",
        "tags": [
          "Billing"
        ],
        "summary": "The organisation's billing state",
        "description": "Subscription, entitlements, credit balance, saved card, active add-ons and what the next charge will be — the whole billing picture in one call.\n\nA legacy organisation has unlimited monthly credits; `entitlements.creditsPerMonth` is serialised as `null` for those, since infinity is not valid JSON. Treat null as unlimited, not as zero.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The billing state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/checkout": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "post": {
        "operationId": "startCheckout",
        "tags": [
          "Billing"
        ],
        "summary": "Begin a subscription",
        "description": "Owners and admins. Mints a short-lived checkout session and returns its `identifier`; the browser then tokenises the card and finishes with `POST .../billing/confirm`.\n\nNo money moves here. For an agency-billed organisation whose agency has no usable billing account, this fails immediately rather than letting the customer through card capture only to fail at the charge.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tier",
                  "interval"
                ],
                "properties": {
                  "tier": {
                    "type": "string",
                    "description": "A tier from `GET /api/v1/billing/plans`."
                  },
                  "interval": {
                    "type": "string",
                    "enum": [
                      "monthly",
                      "yearly"
                    ]
                  },
                  "purpose": {
                    "type": "string",
                    "description": "What the session is for — a new subscription, or replacing a card."
                  },
                  "legalName": {
                    "type": "string",
                    "description": "Required for invoicing."
                  },
                  "companyNumber": {
                    "type": "string",
                    "description": "Required for invoicing."
                  },
                  "language": {
                    "type": "string",
                    "enum": [
                      "en",
                      "he"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Session created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "identifier": {
                      "type": "string",
                      "description": "Pass to `POST .../billing/confirm`."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "503": {
            "description": "The billing account for this organisation is not configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/confirm": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "post": {
        "operationId": "confirmCheckout",
        "tags": [
          "Billing"
        ],
        "summary": "Finish checkout and take the first charge",
        "description": "Where money actually moves. Send the checkout `identifier` and the single-use card token from the browser.\n\nA declined card is a **402** with `code: payment_failed` and the provider's reason — distinct from a 502, which means the provider itself could not be reached and the outcome is unknown. Retrying is safe for the first; for the second, check the state before charging again.\n\nCalling this for a subscription that is already active returns `ok: true` with the existing subscription rather than charging twice.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "identifier",
                  "singleUseToken"
                ],
                "properties": {
                  "identifier": {
                    "type": "string"
                  },
                  "singleUseToken": {
                    "type": "string"
                  },
                  "cardLast4": {
                    "type": "string",
                    "pattern": "^[0-9]{4}$"
                  },
                  "cardExpMonth": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 12
                  },
                  "cardExpYear": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscribed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "subscription": {
                      "$ref": "#/components/schemas/Subscription"
                    },
                    "documentUrl": {
                      "type": "string",
                      "nullable": true,
                      "description": "The invoice, where one was issued."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad body, or a checkout session that is unknown or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "The card was declined. `message` carries the provider's reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "502": {
            "description": "The payment provider could not be reached. The outcome is unknown — check the state before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/return/{pendingRef}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        },
        {
          "name": "pendingRef",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getCheckoutReturn",
        "tags": [
          "Billing"
        ],
        "summary": "Resolve a returning checkout",
        "description": "Where the browser lands after a hosted payment page. Reports what became of the attempt.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "The outcome.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/preview-change": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "previewBillingChange",
        "tags": [
          "Billing"
        ],
        "summary": "What would this change cost?",
        "description": "Prices a tier change or an add-on before you commit to it — proration now, and what the next full charge becomes. Pass exactly one of `tier` or `addon`.\n\n`kind` names the shape of the change: an upgrade charges the prorated difference now; a `scheduled_downgrade` charges nothing and takes effect at the period end; a `revert` undoes a scheduled change.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "parameters": [
          {
            "name": "tier",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "addon",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The quote.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "kind": {
                      "type": "string",
                      "description": "e.g. `upgrade`, `scheduled_downgrade`, `revert`."
                    },
                    "chargeNowAgorot": {
                      "type": "integer",
                      "description": "Excluding VAT."
                    },
                    "chargeNowWithVatAgorot": {
                      "type": "integer"
                    },
                    "remainingDays": {
                      "type": "integer"
                    },
                    "periodEnd": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "nextCharge": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Neither or both of `tier` and `addon`, an unknown value, or a change that does not apply.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/change-plan": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "post": {
        "operationId": "changePlan",
        "tags": [
          "Billing"
        ],
        "summary": "Move to another tier",
        "description": "An upgrade charges the prorated difference immediately against the saved card. A downgrade is scheduled for the period end instead, so nobody loses paid-for capacity mid-month. Use `GET .../billing/preview-change` first to see which you are about to do.\n\nA second change while one is still settling is refused with `409 billing_change_in_progress` rather than queued — two concurrent changes to the same subscription is not a state worth having.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tier"
                ],
                "properties": {
                  "tier": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Changed, scheduled, or reverted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "reverted": {
                      "type": "boolean",
                      "description": "Present when this undid a scheduled change."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown tier, already on it, no active subscription, or no saved card.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "The card was declined.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Another billing change is still settling.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "502": {
            "description": "The payment provider could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/cancel": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "post": {
        "operationId": "cancelSubscription",
        "tags": [
          "Billing"
        ],
        "summary": "Cancel at the end of the period",
        "description": "Nothing is cut off immediately — the subscription runs to `effectiveAt`, which is the end of the period already paid for.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Scheduled to end.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "effectiveAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No active subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/topup": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "post": {
        "operationId": "buyCreditPack",
        "tags": [
          "Billing"
        ],
        "summary": "Buy a credit pack",
        "description": "Charges the saved card for one of the packs from `GET /api/v1/billing/plans` and returns the new balance. Requires a paid plan.\n\nA second top-up while one is in flight is refused with `409 topup_in_progress` — a double-click must not buy two packs.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "credits"
                ],
                "properties": {
                  "credits": {
                    "type": "integer",
                    "description": "Must match a pack size exactly."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bought.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "balance": {
                      "type": "number",
                      "description": "The new total."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not a valid pack size, or no saved card.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Credit packs need a paid plan (`code: feature_locked`), or the card was declined.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "A top-up is already in progress.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "502": {
            "description": "The payment provider could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/addons": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "post": {
        "operationId": "toggleAddon",
        "tags": [
          "Billing"
        ],
        "summary": "Turn a paid add-on on or off",
        "description": "Add-ons such as SignFlow Connect and signer payments. Enabling one charges the prorated remainder of the current period.\n\nThe distinct 400 codes matter: `addon_included` means the plan already covers it, `addon_not_available` means this plan cannot have it, and `addon_already_active` means it is already on. None of them is a failure to retry.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "addonKey",
                  "enabled"
                ],
                "properties": {
                  "addonKey": {
                    "type": "string"
                  },
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Applied.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown add-on, no active subscription, no saved card, or one of the `addon_*` codes above.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "The card was declined.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Another billing change is still settling.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          },
          "502": {
            "description": "The payment provider could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/activity": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "listBillingActivity",
        "tags": [
          "Billing"
        ],
        "summary": "Billing history",
        "description": "Charges, refunds, credit grants and plan changes, newest first. Paginate with `before`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "parameters": [
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "Cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of activity.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/invoices": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "listInvoices",
        "tags": [
          "Billing"
        ],
        "summary": "List invoices",
        "description": "Fetched from the billing provider. An organisation that has never been charged gets an empty list, not an error. While a bank-transfer organisation's documents are pinned to a Sumit customer by a platform owner, the list shows only the documents SignFlow issued for this organisation on that customer.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The invoices.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invoices": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "502": {
            "description": "The billing provider could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/method": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "put": {
        "operationId": "setBillingMethod",
        "tags": [
          "Billing"
        ],
        "summary": "Switch between card and bank transfer",
        "description": "Owners only. Bank transfer bills on net terms against an issued invoice instead of charging a card.\n\nQuarterly billing is only available on bank transfer — asking for it on a card subscription is refused with `quarterly_requires_bank_transfer`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "billingMethod": {
                    "type": "string",
                    "enum": [
                      "credit_card",
                      "bank_transfer"
                    ]
                  },
                  "paymentTermsDays": {
                    "type": "integer",
                    "description": "Net terms for bank transfer. Defaults to 30."
                  },
                  "billingInterval": {
                    "type": "string",
                    "enum": [
                      "monthly",
                      "quarterly",
                      "yearly"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Set.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "An invalid method or interval, or quarterly without bank transfer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Owners only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/auto-reload": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "put": {
        "operationId": "setAutoReload",
        "tags": [
          "Billing"
        ],
        "summary": "Buy credits automatically when the balance runs low",
        "description": "When the balance falls below `threshold`, a pack of `packCredits` is bought against the saved card. The threshold must be **below** the pack size — otherwise buying a pack would leave you still under the threshold and it would fire again immediately, which is why `threshold_gte_pack` is refused.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled"
                ],
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "packCredits": {
                    "type": "integer",
                    "description": "Must match a pack size."
                  },
                  "threshold": {
                    "type": "integer",
                    "description": "Must be less than `packCredits`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Set.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_pack`, `invalid_threshold`, or `threshold_gte_pack`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No subscription to attach this to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/bank-transfer/invoices": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "listOpenBankTransferInvoices",
        "tags": [
          "Billing"
        ],
        "summary": "List unpaid bank-transfer invoices",
        "description": "What the organisation still owes on net terms.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The open invoices.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invoices": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "issueBankTransferInvoice",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Issue a bank-transfer invoice",
        "description": "Platform owner only. Issues one חשבונית עסקה in Sumit with the given lines, due on the organisation's payment terms. Amounts are in agorot, before VAT, and the total may not exceed 10,000,000 agorot. It never changes the plan or the billing period.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "lines"
                ],
                "properties": {
                  "lines": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 10,
                    "items": {
                      "type": "object",
                      "required": [
                        "label",
                        "amountAgorot"
                      ],
                      "properties": {
                        "label": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200
                        },
                        "amountAgorot": {
                          "type": "integer",
                          "minimum": 1
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invoice issued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "invoiceId": {
                      "type": "string"
                    },
                    "documentId": {
                      "type": "integer"
                    },
                    "documentUrl": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_lines: the lines are missing, malformed, or total more than 10,000,000 agorot. Also returned (with `Invalid JSON body`) when the request body is not valid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "owner_only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "org_not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "not_bank_transfer: the organisation is not on bank-transfer billing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "payment_provider_error: Sumit refused or failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "agency_billing_not_configured: the billing account for this organisation is not configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/bank-transfer/{invoiceId}/mark-paid": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        },
        {
          "name": "invoiceId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "markBankTransferInvoicePaid",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Record a bank transfer as received",
        "description": "Platform admins only. A bank transfer arrives outside SignFlow, so somebody has to say it landed; this is that step, and it is what re-opens the account.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Marked paid.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/bank-transfer/{invoiceId}/void": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        },
        {
          "name": "invoiceId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "voidBankTransferInvoice",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Void a bank-transfer invoice",
        "description": "Platform owner only. Closes the open חשבונית עסקה in Sumit first (Sumit does not cancel this document type: it is not an accounting document, so it is marked closed, not credited), then marks the invoice void. A document already closed in Sumit is not an error (`alreadyClosed: true`). If the organisation was suspended and no other invoice is overdue, its suspension is lifted. Agency wholesale invoices are refused.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reason"
                ],
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invoice voided.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "alreadyClosed": {
                      "type": "boolean",
                      "description": "The document was already closed in Sumit (e.g. by hand)."
                    },
                    "restored": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_reason: the reason is missing or longer than 200 characters. Also returned (with `Invalid JSON body`) when the request body is not valid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "owner_only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "org_not_found or invoice_not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "not_open, no_document, wholesale_not_supported, or void_conflict.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "payment_provider_error: Sumit refused or failed, or did not confirm the document is closed; `message` says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "agency_billing_not_configured: the billing account for this organisation is not configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/sumit-customer": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "getSumitCustomerPin",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Get the pinned Sumit customer",
        "description": "Platform owner only, browser session only. The Sumit customer this organisation's bank-transfer documents are pinned to, or `null` when Sumit finds the customer by the organisation id (the default). `customerUrl` links to that customer's page in Sumit; it is `null` when Sumit could not be reached.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The pin.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sumitCustomerId": {
                      "type": "integer",
                      "nullable": true
                    },
                    "customerUrl": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "session_required (an API token or MCP token was used) or owner_only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "org_not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "setSumitCustomerPin",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Pin bank-transfer documents to a Sumit customer",
        "description": "Platform owner only, browser session only. Sends this organisation's bank-transfer documents (חשבונית עסקה and the later חשבונית מס/קבלה) to an existing Sumit customer, addressed by its Sumit customer number, instead of the customer Sumit finds by the organisation id. Card charges, the saved card and standing orders are unaffected. `null` clears the pin.\n\nA pin is set only on a bank-transfer organisation billed through LeonAI's own Sumit account, and only after Sumit confirms the customer exists. Setting or clearing is refused while the organisation has an open invoice, so an invoice and its receipt never land on different customers. Every change is recorded in the billing events with the old and new number, the reason and who made it.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sumitCustomerId",
                  "reason"
                ],
                "properties": {
                  "sumitCustomerId": {
                    "type": "integer",
                    "nullable": true,
                    "minimum": 1,
                    "description": "The Sumit customer number, or `null` to clear the pin."
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved, or `unchanged: true` when the value was already set (nothing is written).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "sumitCustomerId": {
                      "type": "integer",
                      "nullable": true
                    },
                    "customerUrl": {
                      "type": "string",
                      "nullable": true
                    },
                    "unchanged": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_customer_id (not a positive integer or null), invalid_reason (missing or longer than 200 characters), or sumit_customer_not_found (Sumit does not know the number; `message` carries Sumit's text). Also returned (with `Invalid JSON body`) when the request body is not valid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "session_required (an API token or MCP token was used) or owner_only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "org_not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "not_bank_transfer, agency_scoped (billed through an agency's own Sumit account), or open_invoices (void them first).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "payment_provider_error: Sumit could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/admin/comp": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "post": {
        "operationId": "compPlan",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Give an organisation a plan for free",
        "description": "Platform admins only. Grants a tier without charging for it.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tier"
                ],
                "properties": {
                  "tier": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Comped.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "description": "The billing provider could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/admin/uncomp": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "post": {
        "operationId": "uncompPlan",
        "tags": [
          "Platform Admin"
        ],
        "summary": "End a comped plan",
        "description": "Platform admins only. Refused with `not_comped` if the subscription was never comped in the first place.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Reverted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The subscription is not comped.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/admin/credits": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "post": {
        "operationId": "adjustCredits",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Add or remove credits by hand",
        "description": "Platform admins only. `delta` may be negative to claw credits back. A `reason` is required — an unexplained balance change is not something anybody can reconcile later.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "delta",
                  "reason"
                ],
                "properties": {
                  "delta": {
                    "type": "number",
                    "description": "Non-zero. Negative removes credits."
                  },
                  "reason": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Adjusted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "balance": {
                      "type": "number"
                    },
                    "clawed": {
                      "type": "number",
                      "description": "How much was actually taken back, when a negative delta exceeded the balance."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/admin/cancel-pack": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "post": {
        "operationId": "cancelCreditPack",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Cancel a credit grant",
        "description": "Platform admins only. Removes a pack's unspent credits — for a refund or a mistake. A grant that has already been consumed is not cancelable.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "grantId"
                ],
                "properties": {
                  "grantId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "canceled": {
                      "type": "number"
                    },
                    "balance": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No such grant, or it cannot be cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/admin/reconcile": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "post": {
        "operationId": "reconcileBilling",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Re-sync with the billing provider",
        "description": "Platform admins only. Pulls the provider's view and settles any disagreement with SignFlow's — the repair step after a webhook was missed.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Reconciled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "502": {
            "description": "The billing provider could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodedError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/organizations/{orgId}/billing/admin/legacy-alignment": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrgIdPath"
        }
      ],
      "get": {
        "operationId": "previewLegacyAlignment",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Dry-run the legacy alignment",
        "description": "Platform admins only. Shows which legacy organisations would move to which tier, without moving any of them. Always run this before the POST.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "What would happen.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "applyLegacyAlignment",
        "tags": [
          "Platform Admin"
        ],
        "summary": "Move legacy organisations onto a tier",
        "description": "Platform **owners** only, and rate-limited hard: this rewrites the plan of every organisation you name. Run the GET first.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 5,
          "seconds": 300
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "orgIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "tier": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Applied.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Platform owners only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/billing/sumit-webhook": {
      "post": {
        "operationId": "sumitBillingWebhook",
        "tags": [
          "Billing"
        ],
        "summary": "Billing provider webhook",
        "description": "Called by the payment provider, not by you. Authenticated by a shared secret in the query string.",
        "security": [],
        "parameters": [
          {
            "name": "secret",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Received.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "received": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/billing/agency-webhook/{token}": {
      "parameters": [
        {
          "name": "token",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "agencyBillingWebhook",
        "tags": [
          "Billing"
        ],
        "summary": "Agency billing provider webhook",
        "description": "The white-label counterpart: each agency bills through its own provider account, so each gets its own webhook endpoint, identified by the token in the path.",
        "security": [],
        "parameters": [
          {
            "name": "secret",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Received.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "received": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/agencies": {
      "get": {
        "operationId": "listAgencies",
        "tags": [
          "Agencies"
        ],
        "summary": "List agencies",
        "description": "Platform admins only. An agency is a white-label reseller: it has its own brand, hostnames, price book and billing account, and the organisations it signs up are its clients.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The agencies.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Agency"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "createAgency",
        "tags": [
          "Agencies"
        ],
        "summary": "Create an agency",
        "description": "Platform admins only. Creates the agency, its own workspace organisation, and a fallback hostname on the neutral domain so it is reachable before it configures a domain of its own.\n\nThe name fields reject `(`, `)`, `\\` and control characters. That is not fussiness: these strings are drawn into generated PDFs, where those characters would break the output.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 3600
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "companyName",
                  "slug",
                  "productName",
                  "legalEntity",
                  "supportEmail"
                ],
                "properties": {
                  "companyName": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string",
                    "description": "Lowercase letters, digits and hyphens."
                  },
                  "productName": {
                    "type": "string",
                    "description": "What the agency calls the product to its own customers."
                  },
                  "legalEntity": {
                    "type": "string",
                    "description": "The name that appears on documents and invoices."
                  },
                  "supportEmail": {
                    "type": "string",
                    "format": "email"
                  },
                  "supportPhone": {
                    "type": "string"
                  },
                  "defaultLanguage": {
                    "type": "string",
                    "enum": [
                      "en",
                      "he"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "organization_id": {
                          "type": "string",
                          "description": "The agency's own workspace."
                        },
                        "fallback_hostname": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "The slug or the fallback hostname is taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/agencies/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getAgency",
        "tags": [
          "Agencies"
        ],
        "summary": "Get an agency",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The agency.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Agency"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateAgency",
        "tags": [
          "Agencies"
        ],
        "summary": "Update an agency",
        "description": "Partial. Branding and support details are open to agency staff and platform admins; **wholesale terms — the rate the agency pays SignFlow — are platform owners only**, and are rejected for anyone else even in an otherwise valid body.\n\nSuspending an agency stops its clients being created, without deleting anything.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgencyUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "A value failed validation, or nothing recognisable was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not permitted — or wholesale terms were sent by someone who is not a platform owner.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/agencies/{id}/members": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "addAgencyMember",
        "tags": [
          "Agencies"
        ],
        "summary": "Add agency staff",
        "description": "`userId` accepts an email address as well as an id. Agency staff are held here, not as members of the agency's workspace organisation.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "userId",
                  "role"
                ],
                "properties": {
                  "userId": {
                    "type": "string",
                    "description": "A user id or an email address."
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "owner",
                      "admin"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Added.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Already a member.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/agencies/{id}/members/{memberId}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "memberId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "operationId": "removeAgencyMember",
        "tags": [
          "Agencies"
        ],
        "summary": "Remove agency staff",
        "description": "The last agency owner cannot be removed.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "That is the last agency owner.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/agencies/{id}/clients": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listAgencyClients",
        "tags": [
          "Agencies"
        ],
        "summary": "List an agency's client organisations",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The clients.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "assignAgencyClient",
        "tags": [
          "Agencies"
        ],
        "summary": "Move an organisation under an agency",
        "description": "Platform **owners** only — this changes who bills the organisation. An agency's own workspace cannot be made its own client.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "organizationId"
                ],
                "properties": {
                  "organizationId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Assigned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/agencies/{id}/clients/{orgId}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "orgId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "operationId": "unassignAgencyClient",
        "tags": [
          "Agencies"
        ],
        "summary": "Return an organisation to SignFlow-direct",
        "description": "Platform owners only.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Unassigned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/agencies/{id}/assets/{kind}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "kind",
          "in": "path",
          "required": true,
          "description": "Which asset — e.g. the logo, or the favicon.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "operationId": "uploadAgencyAsset",
        "tags": [
          "Agencies"
        ],
        "summary": "Upload a brand asset",
        "description": "PNG, JPEG or ICO, up to 2 MB. Served publicly from `GET /api/v1/agency-assets/{agencyId}/{kind}` — these are brand images, meant to be fetched by anyone loading an agency-branded page.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/agency-assets/{agencyId}/{kind}": {
      "parameters": [
        {
          "name": "agencyId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "kind",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getAgencyAsset",
        "tags": [
          "Agencies"
        ],
        "summary": "Fetch an agency brand asset",
        "description": "Unauthenticated: a logo has to load on a sign-in page, before anyone is signed in.",
        "security": [],
        "responses": {
          "200": {
            "description": "The image.",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agencies/{id}/certificate": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getAgencyCertificate",
        "tags": [
          "Agencies"
        ],
        "summary": "Read the signing certificate",
        "description": "Metadata only — subject, issuer, validity. The private key never leaves the server, encrypted at rest.\n\nThis is the certificate that signs the agency's clients' PDFs. A self-signed one is accepted by readers but shows as untrusted; a certificate from a recognised authority is what turns that indicator green.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The certificate, or null if none is configured.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "certificate": {
                      "type": "object",
                      "additionalProperties": true,
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteAgencyCertificate",
        "tags": [
          "Agencies"
        ],
        "summary": "Remove the signing certificate",
        "description": "Signing falls back to the platform's certificate.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/agencies/{id}/certificate/generate": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "generateAgencyCertificate",
        "tags": [
          "Agencies"
        ],
        "summary": "Generate a self-signed certificate",
        "description": "Enough to sign documents in the agency's own name. Readers will show the signature as valid but not trusted — for a trusted indicator, upload a certificate from a recognised authority instead.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Generated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "certificate": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "description": "The platform's certificate-encryption key is missing or unusable, so nothing can be stored safely.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agencies/{id}/certificate/upload": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "uploadAgencyCertificate",
        "tags": [
          "Agencies"
        ],
        "summary": "Upload a signing certificate",
        "description": "A PKCS#12 bundle and its passphrase, up to 32 KB. The bundle is opened and re-encrypted with the platform key; the passphrase is not kept.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 10,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "passphrase"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "passphrase": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "certificate": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No file, no passphrase, too large, or a bundle that would not open (`invalid_certificate`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "description": "The platform's certificate-encryption key is missing or unusable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agencies/{id}/auth-apps": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listAgencyAuthApps",
        "tags": [
          "Agencies"
        ],
        "summary": "List the agency's sign-in apps",
        "description": "So users sign in through the agency's own Google or Microsoft app, and see the agency's name on the consent screen rather than SignFlow's. Client secrets are never returned.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The configured apps.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agencies/{id}/auth-apps/{provider}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "provider",
          "in": "path",
          "required": true,
          "description": "`google` or `microsoft`.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "operationId": "setAgencyAuthApp",
        "tags": [
          "Agencies"
        ],
        "summary": "Configure a sign-in app",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string",
                    "description": "Stored encrypted and never read back."
                  },
                  "authority": {
                    "type": "string",
                    "description": "The tenant authority, for Microsoft."
                  },
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown provider, or a client id the provider would not accept.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "operationId": "deleteAgencyAuthApp",
        "tags": [
          "Agencies"
        ],
        "summary": "Remove a sign-in app",
        "description": "Sign-in falls back to the platform's own app.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agencies/{id}/billing-account": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getAgencyBillingAccount",
        "tags": [
          "Agencies"
        ],
        "summary": "Read the agency's billing account",
        "description": "So the agency charges its own clients through its own provider account and receives the money directly. Credentials are never returned.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The account, or null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true,
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "setAgencyBillingAccount",
        "tags": [
          "Agencies"
        ],
        "summary": "Configure the agency's billing account",
        "description": "Credentials are verified before being stored, then encrypted. The response carries the webhook URL to register with the provider — each agency gets its own.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 20,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "description": "Provider id plus the credentials that provider needs."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Configured.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhookUrl": {
                      "type": "string",
                      "description": "Register this with the provider."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The provider rejected the credentials (`invalid_credentials`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "description": "The platform's agency-billing encryption key is missing or unusable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteAgencyBillingAccount",
        "tags": [
          "Agencies"
        ],
        "summary": "Remove the agency's billing account",
        "description": "Its clients can no longer be charged until another is configured.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agencies/{id}/wholesale-invoices": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listWholesaleInvoices",
        "tags": [
          "Agencies"
        ],
        "summary": "List wholesale invoices",
        "description": "What the agency owes SignFlow — the other side of what it charges its own clients.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The invoices.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agencies/{id}/wholesale-invoices/{invoiceId}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "invoiceId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "patch": {
        "operationId": "adjustWholesaleInvoice",
        "tags": [
          "Agencies"
        ],
        "summary": "Adjust a draft wholesale invoice",
        "description": "Only a **draft** invoice can be adjusted, and the adjusted total must stay positive — an issued invoice is a financial record and does not change afterwards.\n\n`adjustmentAgorot` may be negative for a credit. A note is required: an unexplained adjustment is not reconcilable later.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "adjustmentAgorot",
                  "adjustmentNote"
                ],
                "properties": {
                  "adjustmentAgorot": {
                    "type": "integer",
                    "description": "In agorot. Negative for a credit."
                  },
                  "adjustmentNote": {
                    "type": "string",
                    "maxLength": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Adjusted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "Not a draft, a missing note, or an adjustment that would take the total to zero or below.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agencies/{id}/wholesale-invoices/{invoiceId}/issue": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "invoiceId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "issueWholesaleInvoice",
        "tags": [
          "Agencies"
        ],
        "summary": "Issue a wholesale invoice",
        "description": "Turns the draft into a real invoice. After this it can no longer be adjusted, so make any corrections first.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Issued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agencies/{id}/hostnames": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "addAgencyHostname",
        "tags": [
          "Agencies"
        ],
        "summary": "Add a hostname",
        "description": "The agency's own domain, so its customers never see SignFlow's. Point a CNAME at `dns_target`, then call `.../hostnames/verify`.\n\nThere is a cap on hostnames per agency (`hostname_limit`), and a hostname already claimed elsewhere is refused (`hostname_in_use`).",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "hostname"
                ],
                "properties": {
                  "hostname": {
                    "type": "string",
                    "example": "sign.reseller.example"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered, not yet verified.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "hostname": {
                          "type": "string"
                        },
                        "cf_hostname_id": {
                          "type": "string"
                        },
                        "verified": {
                          "type": "boolean"
                        },
                        "dns_target": {
                          "type": "string",
                          "description": "Point a CNAME here."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`hostname_limit` or `hostname_in_use`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The hostname could not be registered upstream.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Custom hostnames are not configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agencies/{id}/hostnames/verify": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "verifyAgencyHostname",
        "tags": [
          "Agencies"
        ],
        "summary": "Verify a hostname",
        "description": "Re-checks DNS and the certificate. `ssl` matters as much as `verified` — a hostname can resolve before its certificate is issued, and it is not usable until both are done.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "hostname"
                ],
                "properties": {
                  "hostname": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The current status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "verified": {
                          "type": "boolean"
                        },
                        "hostnameStatus": {
                          "type": "string"
                        },
                        "ssl": {
                          "type": "string",
                          "description": "Certificate state."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The hostname is claimed elsewhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The status check failed upstream.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Custom hostnames are not configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agencies/{id}/hostnames/status": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getAgencyHostnameStatus",
        "tags": [
          "Agencies"
        ],
        "summary": "Read a hostname's status",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "parameters": [
          {
            "name": "hostname",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agencies/{id}/hostnames/{hostname}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "hostname",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "operationId": "deleteAgencyHostname",
        "tags": [
          "Agencies"
        ],
        "summary": "Remove a hostname",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agencies/{id}/hostnames/fallback": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "createAgencyFallbackHostname",
        "tags": [
          "Agencies"
        ],
        "summary": "Issue a fallback hostname",
        "description": "A subdomain on SignFlow's neutral domain — neutral because it carries neither brand, so an agency is reachable before its own domain is set up without advertising SignFlow to its customers.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Issued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "hostname": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The hostname is claimed elsewhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "No neutral domain is configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agency/status": {
      "get": {
        "operationId": "getAgencyConsoleStatus",
        "tags": [
          "Agency Console"
        ],
        "summary": "Am I agency staff, and for which agency?",
        "description": "The `/api/v1/agency/*` family is the console an agency uses to run itself. There is no agency id in these paths — it comes from who you are, which is what keeps one agency out of another's console.\n\n`agencyId: null` means the caller is not agency staff. `legalReviewedAt` being null blocks client creation until the agency has accepted its terms; `suspended` blocks it outright.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Your standing.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "agencyId": {
                          "type": "string",
                          "nullable": true
                        },
                        "role": {
                          "type": "string",
                          "nullable": true,
                          "enum": [
                            "owner",
                            "admin",
                            null
                          ]
                        },
                        "legalReviewedAt": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        },
                        "suspended": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/agency/legal-review": {
      "post": {
        "operationId": "acceptAgencyLegalReview",
        "tags": [
          "Agency Console"
        ],
        "summary": "Record that the agency has accepted its terms",
        "description": "Until this is done, creating client organisations is refused. Reselling under your own brand carries obligations to the customers you sign up, and this is the acknowledgement of them.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "legalReviewedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agency/brand": {
      "get": {
        "operationId": "getAgencyBrand",
        "tags": [
          "Agency Console"
        ],
        "summary": "Read the agency's branding",
        "description": "Product name, colours, logos, support details and default language — everything that makes the product look like the agency's own.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The branding.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AgencyBrand"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateAgencyBrand",
        "tags": [
          "Agency Console"
        ],
        "summary": "Update the agency's branding",
        "description": "Partial. Image URLs must be a SignFlow storage path or an `https://` URL, and colours must be `#RRGGBB`. Wholesale terms are not settable here — those belong to the platform.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgencyBrand"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "A value failed validation, or nothing recognisable was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agency/assets/{kind}": {
      "parameters": [
        {
          "name": "kind",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "operationId": "uploadOwnAgencyAsset",
        "tags": [
          "Agency Console"
        ],
        "summary": "Upload a brand asset",
        "description": "PNG, JPEG or ICO, up to 2 MB. The agency-console twin of `PUT /api/v1/agencies/{id}/assets/{kind}`.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agency/clients": {
      "get": {
        "operationId": "listOwnAgencyClients",
        "tags": [
          "Agency Console"
        ],
        "summary": "List the agency's clients",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The clients.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "createAgencyClient",
        "tags": [
          "Agency Console"
        ],
        "summary": "Sign up a client",
        "description": "Creates a client organisation under the agency and invites its owner. Refused while the agency is suspended or has not completed its legal review.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "slug",
                  "ownerEmail"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string"
                  },
                  "ownerEmail": {
                    "type": "string",
                    "format": "email"
                  },
                  "logoUrl": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created and invited.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "invite_id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "That slug is taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agency/clients/{orgId}/status": {
      "parameters": [
        {
          "name": "orgId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "patch": {
        "operationId": "setAgencyClientStatus",
        "tags": [
          "Agency Console"
        ],
        "summary": "Suspend or reinstate a client",
        "description": "For a client who has stopped paying the agency. Suspension blocks use without deleting anything, so reinstating restores everything as it was.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "suspended"
                ],
                "properties": {
                  "suspended": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agency/clients/{orgId}/usage": {
      "parameters": [
        {
          "name": "orgId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getAgencyClientUsage",
        "tags": [
          "Agency Console"
        ],
        "summary": "Read a client's usage",
        "description": "What the client has consumed, priced against the agency's own price book — the basis for what the agency bills them.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The usage.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agency/price-book": {
      "get": {
        "operationId": "getPriceBook",
        "tags": [
          "Agency Console"
        ],
        "summary": "Read the agency's price book",
        "description": "What the agency charges its own clients — plans, add-ons and credit packs. `overriddenFields` says which entries the agency has actually set; everything else falls through to SignFlow's list prices, so you can tell a deliberate price from an inherited default.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The price book.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "book": {
                          "$ref": "#/components/schemas/PriceBook"
                        },
                        "overriddenFields": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Which entries the agency set itself, as opposed to inheriting."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "put": {
        "operationId": "setPriceBook",
        "tags": [
          "Agency Console"
        ],
        "summary": "Set the agency's prices",
        "description": "Prices are in agorot and exclude VAT. Every change is recorded — see `GET /api/v1/agency/price-book/history`, which is what makes a customer's \"why did my price change?\" answerable.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PriceBook"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "The price book failed validation; `error` says how.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agency/price-book/history": {
      "get": {
        "operationId": "getPriceBookHistory",
        "tags": [
          "Agency Console"
        ],
        "summary": "Price-book change history",
        "description": "Every change, with who made it and when.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The history.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agency/wholesale-invoices": {
      "get": {
        "operationId": "listOwnWholesaleInvoices",
        "tags": [
          "Agency Console"
        ],
        "summary": "What the agency owes SignFlow",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The invoices.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/agency/billing-account": {
      "get": {
        "operationId": "getOwnAgencyBillingAccount",
        "tags": [
          "Agency Console"
        ],
        "summary": "Read the agency's billing account",
        "description": "Credentials are never returned.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The account, or null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true,
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "put": {
        "operationId": "setOwnAgencyBillingAccount",
        "tags": [
          "Agency Console"
        ],
        "summary": "Configure the agency's billing account",
        "description": "Verified before storage, then encrypted. Register the returned `webhookUrl` with the provider.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Configured.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhookUrl": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The provider rejected the credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "description": "The platform's agency-billing encryption key is missing or unusable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteOwnAgencyBillingAccount",
        "tags": [
          "Agency Console"
        ],
        "summary": "Remove the agency's billing account",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agency/billing-account/providers": {
      "get": {
        "operationId": "listBillingProviders",
        "tags": [
          "Agency Console"
        ],
        "summary": "Which billing providers an agency can use",
        "description": "Field names and shapes only — enough to render the setup form without hard-coding it.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The providers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "providers": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/agency/certificate": {
      "get": {
        "operationId": "getOwnAgencyCertificate",
        "tags": [
          "Agency Console"
        ],
        "summary": "Read the signing certificate",
        "description": "Metadata only; the private key never leaves the server.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The certificate, or null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "certificate": {
                      "type": "object",
                      "additionalProperties": true,
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "delete": {
        "operationId": "deleteOwnAgencyCertificate",
        "tags": [
          "Agency Console"
        ],
        "summary": "Remove the signing certificate",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agency/certificate/generate": {
      "post": {
        "operationId": "generateOwnAgencyCertificate",
        "tags": [
          "Agency Console"
        ],
        "summary": "Generate a self-signed certificate",
        "description": "Signatures will validate but show as untrusted. Upload a certificate from a recognised authority for a trusted indicator.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Generated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "certificate": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "description": "The platform's certificate-encryption key is missing or unusable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agency/certificate/upload": {
      "post": {
        "operationId": "uploadOwnAgencyCertificate",
        "tags": [
          "Agency Console"
        ],
        "summary": "Upload a signing certificate",
        "description": "A PKCS#12 bundle and its passphrase, up to 32 KB. The passphrase is not kept.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "passphrase"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "passphrase": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "certificate": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "description": "The platform's certificate-encryption key is missing or unusable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agency/auth-apps": {
      "get": {
        "operationId": "listOwnAgencyAuthApps",
        "tags": [
          "Agency Console"
        ],
        "summary": "List the agency's sign-in apps",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The configured apps. Secrets are never returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/agency/auth-apps/{provider}": {
      "parameters": [
        {
          "name": "provider",
          "in": "path",
          "required": true,
          "description": "`google` or `microsoft`.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "operationId": "setOwnAgencyAuthApp",
        "tags": [
          "Agency Console"
        ],
        "summary": "Configure a sign-in app",
        "description": "So the consent screen carries the agency's name, not SignFlow's.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  },
                  "authority": {
                    "type": "string"
                  },
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteOwnAgencyAuthApp",
        "tags": [
          "Agency Console"
        ],
        "summary": "Remove a sign-in app",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/agency/hostnames": {
      "post": {
        "operationId": "addOwnAgencyHostname",
        "tags": [
          "Agency Console"
        ],
        "summary": "Add a hostname",
        "description": "Point a CNAME at `dns_target`, then verify. Subject to the same per-agency cap as the platform-admin route.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "hostname"
                ],
                "properties": {
                  "hostname": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered, not yet verified.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "hostname": {
                          "type": "string"
                        },
                        "cf_hostname_id": {
                          "type": "string"
                        },
                        "verified": {
                          "type": "boolean"
                        },
                        "dns_target": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`hostname_limit` or `hostname_in_use`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The hostname could not be registered upstream.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Custom hostnames are not configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agency/hostnames/verify": {
      "post": {
        "operationId": "verifyOwnAgencyHostname",
        "tags": [
          "Agency Console"
        ],
        "summary": "Verify a hostname",
        "description": "Both `verified` and `ssl` must be settled before the hostname is usable.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "hostname"
                ],
                "properties": {
                  "hostname": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The current status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "verified": {
                          "type": "boolean"
                        },
                        "hostnameStatus": {
                          "type": "string"
                        },
                        "ssl": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The hostname is claimed elsewhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The status check failed upstream.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Custom hostnames are not configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agency/hostnames/status": {
      "get": {
        "operationId": "getOwnAgencyHostnameStatus",
        "tags": [
          "Agency Console"
        ],
        "summary": "Read the agency's hostnames and their status",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The hostnames.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/agency/hostnames/{hostname}": {
      "parameters": [
        {
          "name": "hostname",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "operationId": "deleteOwnAgencyHostname",
        "tags": [
          "Agency Console"
        ],
        "summary": "Remove a hostname",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/auth/tenant-config": {
      "get": {
        "operationId": "getTenantConfig",
        "tags": [
          "Authentication"
        ],
        "summary": "Branding and sign-in options for this host",
        "description": "The first call a sign-in page makes, and the only one that works before anyone is authenticated. It answers two questions at once: what should this page look like, and how may someone sign in here.\n\nThe answer depends on the host. On an agency's own domain it carries that agency's brand and that agency's OAuth apps, so a reseller's customers never see SignFlow's name or consent screen.",
        "security": [],
        "responses": {
          "200": {
            "description": "The host's configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "brand": {
                      "type": "object",
                      "properties": {
                        "productName": {
                          "type": "string"
                        },
                        "companyName": {
                          "type": "string"
                        },
                        "legalEntity": {
                          "type": "string"
                        },
                        "supportEmail": {
                          "type": "string",
                          "nullable": true
                        },
                        "supportPhone": {
                          "type": "string",
                          "nullable": true
                        },
                        "logoUrl": {
                          "type": "string",
                          "nullable": true
                        },
                        "logoLightUrl": {
                          "type": "string",
                          "nullable": true
                        },
                        "squareIconUrl": {
                          "type": "string",
                          "nullable": true
                        },
                        "faviconUrl": {
                          "type": "string",
                          "nullable": true
                        },
                        "brandColor": {
                          "type": "string",
                          "nullable": true
                        },
                        "brandSecondaryColor": {
                          "type": "string",
                          "nullable": true
                        },
                        "poweredBy": {
                          "type": "boolean",
                          "description": "Whether to show a SignFlow attribution."
                        },
                        "isPlatform": {
                          "type": "boolean",
                          "description": "True on SignFlow's own host, false on an agency's."
                        }
                      }
                    },
                    "defaultLanguage": {
                      "type": "string",
                      "enum": [
                        "en",
                        "he"
                      ]
                    },
                    "auth": {
                      "type": "object",
                      "properties": {
                        "googleEnabled": {
                          "type": "boolean"
                        },
                        "microsoftEnabled": {
                          "type": "boolean"
                        },
                        "microsoftClientId": {
                          "type": "string",
                          "nullable": true
                        },
                        "microsoftAuthority": {
                          "type": "string",
                          "nullable": true
                        },
                        "ssoEnabled": {
                          "type": "boolean"
                        }
                      }
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/auth/google": {
      "get": {
        "operationId": "startGoogleSignIn",
        "tags": [
          "Authentication"
        ],
        "summary": "Begin Google sign-in",
        "description": "Redirects the browser to Google. Interactive by nature — there is no way to complete it from a server, which is why API tokens exist for machine access.",
        "security": [],
        "x-rate-limit": {
          "requests": 200,
          "seconds": 60
        },
        "parameters": [
          {
            "name": "login_hint",
            "in": "query",
            "required": false,
            "description": "Pre-fill the account chooser.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirect",
            "in": "query",
            "required": false,
            "description": "Where to land afterwards. Must be a path on this site — an absolute URL, or a path that could be read as one, is rejected.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to Google."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/auth/google/callback": {
      "get": {
        "operationId": "googleSignInCallback",
        "tags": [
          "Authentication"
        ],
        "summary": "Google sign-in callback",
        "description": "Called by Google, not by you. Sets the session cookie and redirects into the app — or to the 2FA step, where the account requires one.",
        "security": [],
        "x-rate-limit": {
          "requests": 200,
          "seconds": 60
        },
        "responses": {
          "302": {
            "description": "Redirect into the app, or to the second-factor step."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/auth/microsoft": {
      "post": {
        "operationId": "signInWithMicrosoft",
        "tags": [
          "Authentication"
        ],
        "summary": "Sign in with Microsoft",
        "description": "Exchanges an access token the browser obtained from Microsoft for a SignFlow session.\n\nThe token is checked before it is trusted: the right audience, the expected tenant, and a scope that actually reaches Microsoft Graph. Each failure is a distinct 401 message rather than a generic one, because they mean different misconfigurations.\n\nAn organisation that requires SSO refuses this with `sso_required` and names the organisation, so the page can send the user to the right place rather than telling them their password is wrong.",
        "security": [],
        "x-rate-limit": {
          "requests": 200,
          "seconds": 60
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "accessToken"
                ],
                "properties": {
                  "accessToken": {
                    "type": "string"
                  },
                  "redirect": {
                    "type": "string",
                    "description": "A path on this site."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in. The session cookie is set on the response.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "description": "The token was rejected — wrong audience, wrong tenant, wrong scope, or unreadable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Microsoft sign-in is not configured for this host, or the organisation requires SSO (`error: sso_required`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "ssoRequired": {
                      "type": "boolean"
                    },
                    "org": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/auth/2fa/verify": {
      "post": {
        "operationId": "verifyTwoFactorAtSignIn",
        "tags": [
          "Authentication"
        ],
        "summary": "Complete sign-in with a second factor",
        "description": "The second step for an account with 2FA. The token from step one is not a session and will not authenticate anything until this succeeds.\n\n`trustDevice` remembers this browser so it is not asked again; revoke that later from the trusted-devices endpoints.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "description": "A TOTP or recovery code."
                  },
                  "trustDevice": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "description": "No pending 2FA session, one that expired, or a wrong or already-used code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/session": {
      "get": {
        "operationId": "getSession",
        "tags": [
          "Authentication"
        ],
        "summary": "Who am I?",
        "description": "Resolves the session cookie to a user. A token that has only cleared the first of two sign-in steps is **not** reported as authenticated, and neither is a revoked one — the answer reflects the current state, not what the token claims.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 600,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "authenticated": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "user": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "name": {
                          "type": "string",
                          "nullable": true
                        },
                        "avatar": {
                          "type": "string",
                          "nullable": true
                        },
                        "language": {
                          "type": "string",
                          "nullable": true
                        },
                        "theme": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated. `error` distinguishes an invalid token from a revoked one.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "authenticated": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/auth/refresh": {
      "post": {
        "operationId": "refreshSession",
        "tags": [
          "Authentication"
        ],
        "summary": "Renew the session",
        "description": "Exchanges the refresh cookie for a new session. Nothing needs to be sent in the body.",
        "security": [],
        "x-rate-limit": {
          "requests": 300,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Renewed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "No refresh cookie, one that is invalid or expired, or a user that no longer exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/auth/logout": {
      "post": {
        "operationId": "logout",
        "tags": [
          "Authentication"
        ],
        "summary": "Sign out",
        "description": "Clears the cookies and revokes the token, so it stops working even if a copy was kept. Signing out on one device does not sign out the others.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "x-rate-limit": {
          "requests": 200,
          "seconds": 60
        },
        "responses": {
          "200": {
            "description": "Signed out.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/auth/sso/discover/{email}": {
      "parameters": [
        {
          "name": "email",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "email"
          }
        }
      ],
      "get": {
        "operationId": "discoverSso",
        "tags": [
          "SSO"
        ],
        "summary": "Does this address sign in through SSO?",
        "description": "Matches the address's domain against configured connections, so a sign-in page can send someone to their own identity provider instead of asking for a password they do not have.\n\nIt answers only whether a connection exists and which — never anything about the account itself.",
        "security": [],
        "parameters": [
          {
            "name": "orgId",
            "in": "query",
            "required": false,
            "description": "Narrow the lookup to one organisation.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Whether SSO applies.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sso": {
                      "type": "boolean"
                    },
                    "connectionId": {
                      "type": "string"
                    },
                    "providerName": {
                      "type": "string"
                    },
                    "protocol": {
                      "type": "string",
                      "enum": [
                        "saml",
                        "oidc"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/sso/login/{connectionId}": {
      "parameters": [
        {
          "name": "connectionId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "startSsoLogin",
        "tags": [
          "SSO"
        ],
        "summary": "Begin SSO sign-in",
        "description": "Redirects to the organisation's identity provider. Interactive, like the other sign-in flows.",
        "security": [],
        "responses": {
          "302": {
            "description": "Redirect to the identity provider."
          },
          "400": {
            "description": "The connection is not fully configured, or uses an unsupported protocol.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "SSO is not enabled for this organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such connection, or it is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/sso/saml/callback": {
      "post": {
        "operationId": "samlCallback",
        "tags": [
          "SSO"
        ],
        "summary": "SAML assertion consumer",
        "description": "Where the identity provider posts its assertion. Called by the provider, not by you.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "SAMLResponse": {
                    "type": "string"
                  },
                  "RelayState": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "302": {
            "description": "Redirect into the app."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/auth/sso/oidc/callback": {
      "get": {
        "operationId": "oidcCallback",
        "tags": [
          "SSO"
        ],
        "summary": "OIDC redirect handler",
        "description": "Where the identity provider returns after an OIDC sign-in. Called by the provider, not by you.",
        "security": [],
        "responses": {
          "302": {
            "description": "Redirect into the app."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/auth/sso/saml/metadata": {
      "get": {
        "operationId": "getSamlMetadata",
        "tags": [
          "SSO"
        ],
        "summary": "SignFlow's SAML metadata",
        "description": "The service-provider metadata to hand to an identity provider when configuring a connection. Unauthenticated, because it has to be readable by whoever is setting up the other side.",
        "security": [],
        "responses": {
          "200": {
            "description": "The metadata document.",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sso/connections": {
      "get": {
        "operationId": "listSsoConnections",
        "tags": [
          "SSO"
        ],
        "summary": "List an organisation's SSO connections",
        "description": "The organisation comes from the `X-Organization-Id` header or the `orgId` query parameter.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "parameters": [
          {
            "name": "X-Organization-Id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "orgId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The connections. Client secrets are never returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No organisation was given.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "createSsoConnection",
        "tags": [
          "SSO"
        ],
        "summary": "Create an SSO connection",
        "description": "Admins and owners. SAML and OIDC each need their own set of fields; send the ones for your `protocol`.\n\nEvery URL you supply is checked before it is stored — one that resolves to a private or internal address is refused, so a connection form cannot be used to make SignFlow fetch its own network.\n\n`auto_provision` creates accounts for users the provider vouches for; `default_role` decides what they get.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "protocol",
                  "name"
                ],
                "properties": {
                  "protocol": {
                    "type": "string",
                    "enum": [
                      "saml",
                      "oidc"
                    ]
                  },
                  "name": {
                    "type": "string"
                  },
                  "email_domain": {
                    "type": "string",
                    "description": "Addresses at this domain are routed to this connection."
                  },
                  "auto_provision": {
                    "type": "boolean"
                  },
                  "default_role": {
                    "type": "string",
                    "enum": [
                      "owner",
                      "admin",
                      "member"
                    ]
                  },
                  "saml_entity_id": {
                    "type": "string"
                  },
                  "saml_sso_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "saml_slo_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "saml_certificate": {
                    "type": "string"
                  },
                  "saml_name_id_format": {
                    "type": "string"
                  },
                  "oidc_issuer": {
                    "type": "string",
                    "format": "uri"
                  },
                  "oidc_client_id": {
                    "type": "string"
                  },
                  "oidc_client_secret": {
                    "type": "string",
                    "description": "Stored encrypted and never read back."
                  },
                  "oidc_authorization_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "oidc_token_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "oidc_userinfo_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "oidc_scopes": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing protocol or name, or a URL that was refused as unsafe — `error` names the field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/sso/connections/{id}/toggle": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "operationId": "toggleSsoConnection",
        "tags": [
          "SSO"
        ],
        "summary": "Enable or disable a connection",
        "description": "Flips the connection's current state. A disabled connection cannot be used to sign in and is not returned by discovery.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The new state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "enabled": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sso/connections/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "operationId": "deleteSsoConnection",
        "tags": [
          "SSO"
        ],
        "summary": "Delete a connection",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sso/org-policy": {
      "put": {
        "operationId": "setSsoPolicy",
        "tags": [
          "SSO"
        ],
        "summary": "Require SSO for an organisation",
        "description": "Admins and owners. With `requireSso` on, members must sign in through the named connection — Google and Microsoft sign-in are refused for them with `sso_required`.\n\nThe connection must exist, be enabled, and belong to this organisation; anything else is refused, so the policy cannot lock an organisation out of its own account.",
        "security": [
          {
            "SessionAuth": []
          },
          {
            "ApiToken": []
          }
        ],
        "parameters": [
          {
            "name": "X-Organization-Id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "requireSso"
                ],
                "properties": {
                  "requireSso": {
                    "type": "boolean"
                  },
                  "ssoConnectionId": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Set.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "requireSso": {
                          "type": "boolean"
                        },
                        "ssoConnectionId": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No organisation, or a connection that does not exist, is disabled, or belongs elsewhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/oauth/authorize": {
      "get": {
        "operationId": "oauthAuthorize",
        "tags": [
          "OAuth (MCP Connector)"
        ],
        "summary": "Start an OAuth2 authorization (MCP connector)",
        "description": "Entry point of the authorization-code + PKCE (S256) flow for the one registered confidential client — the SignFlow MCP server. Validates the client and redirect URI, parks the request, and redirects the browser to the SignFlow consent page, where the user signs in, picks an organization and approves scopes.\n\nErrors before the redirect URI is validated return JSON with `400`; later errors redirect back to the client with `?error=`. Not meant to be called from code you write — your MCP client drives this for you.",
        "security": [],
        "parameters": [
          {
            "name": "response_type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "code"
              ]
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uri"
            },
            "description": "Must exactly match a registered redirect URI."
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Space-separated API scopes. `admin` is never grantable."
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code_challenge",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code_challenge_method",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "S256"
              ]
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the SignFlow consent page (or back to the client with `?error=` for post-validation failures)."
          },
          "400": {
            "description": "Unknown client or unregistered redirect URI. No redirect is performed for these, by design."
          },
          "503": {
            "description": "MCP OAuth is not configured on this deployment."
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "operationId": "oauthToken",
        "tags": [
          "OAuth (MCP Connector)"
        ],
        "summary": "Exchange a code or refresh token (MCP connector)",
        "description": "Client-authenticated (HTTP Basic or body credentials) token endpoint. `authorization_code` verifies PKCE and single-use, then mints an `sf_o_…` access token (1 hour) and `sf_r_…` refresh token (90 days). `refresh_token` rotates both and re-checks the organization's MCP entitlement — an org that loses access is cut off at the next refresh.\n\nAccess tokens authenticate against the regular API exactly like `sf_` API tokens (send as `Authorization: Bearer sf_o_…`), carrying the scopes approved at consent.",
        "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"
                  },
                  "code_verifier": {
                    "type": "string"
                  },
                  "redirect_uri": {
                    "type": "string"
                  },
                  "refresh_token": {
                    "type": "string"
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The token pair.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "access_token": {
                      "type": "string"
                    },
                    "token_type": {
                      "type": "string",
                      "enum": [
                        "Bearer"
                      ]
                    },
                    "expires_in": {
                      "type": "integer"
                    },
                    "refresh_token": {
                      "type": "string"
                    },
                    "scope": {
                      "type": "string"
                    },
                    "organization_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_grant`, `invalid_request`, `invalid_scope` or `unsupported_grant_type`, RFC 6749 shaped."
          },
          "401": {
            "description": "`invalid_client` — client authentication failed."
          },
          "503": {
            "description": "MCP OAuth is not configured on this deployment."
          }
        }
      }
    },
    "/oauth/revoke": {
      "post": {
        "operationId": "oauthRevoke",
        "tags": [
          "OAuth (MCP Connector)"
        ],
        "summary": "Revoke a grant token (MCP connector)",
        "description": "RFC 7009. Client-authenticated. Revoking either the access or the refresh token kills the whole grant. Returns 200 whether or not the token was found, per the RFC.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  },
                  "token_type_hint": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Done (idempotent)."
          },
          "401": {
            "description": "`invalid_client` — client authentication failed."
          },
          "503": {
            "description": "MCP OAuth is not configured on this deployment."
          }
        }
      }
    },
    "/oauth/introspect": {
      "post": {
        "operationId": "oauthIntrospect",
        "tags": [
          "OAuth (MCP Connector)"
        ],
        "summary": "Introspect an access token (MCP connector)",
        "description": "RFC 7662. Client-authenticated (HTTP Basic or body credentials — the one registered confidential client only). Answers `{\"active\": false}` with `200` for every unknown, revoked, expired or non-access token, so token existence never leaks. An active response carries the standard members (`scope`, `client_id`, `token_type`, `sub`, `exp`, `iat`) plus two SignFlow extension members the MCP server forwards to tools: `org_id` and `email`.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "The sf_o_ access token to introspect."
                  },
                  "token_type_hint": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Introspection result — `active: true` with metadata, or `active: false`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "active"
                  ],
                  "properties": {
                    "active": {
                      "type": "boolean"
                    },
                    "scope": {
                      "type": "string",
                      "description": "Space-separated granted scopes."
                    },
                    "client_id": {
                      "type": "string"
                    },
                    "token_type": {
                      "type": "string",
                      "enum": [
                        "Bearer"
                      ]
                    },
                    "sub": {
                      "type": "string",
                      "description": "SignFlow user id."
                    },
                    "exp": {
                      "type": "integer",
                      "description": "Expiry, epoch seconds."
                    },
                    "iat": {
                      "type": "integer",
                      "description": "Grant creation, epoch seconds."
                    },
                    "org_id": {
                      "type": "string",
                      "description": "SignFlow extension: the organization the grant is bound to."
                    },
                    "email": {
                      "type": "string",
                      "description": "SignFlow extension: the granting user's email."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` — no token in the body."
          },
          "401": {
            "description": "`invalid_client` — client authentication failed."
          },
          "503": {
            "description": "MCP OAuth is not configured on this deployment."
          }
        }
      }
    },
    "/api/v1/oauth/consent/{requestId}": {
      "get": {
        "operationId": "getOauthConsent",
        "tags": [
          "OAuth (MCP Connector)"
        ],
        "summary": "Read a pending authorization request",
        "description": "What the SignFlow consent page renders: the requesting client, the requested scopes with human descriptions, and the signed-in user's organizations annotated with MCP eligibility. Browser session only — API tokens are rejected so a leaked token can never approve new credentials for itself.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "parameters": [
          {
            "name": "requestId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The pending request.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "client_name": {
                          "type": "string"
                        },
                        "requested_scopes": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "grantable_scopes": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "scope": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "organizations": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "role": {
                                "type": "string"
                              },
                              "mcp_enabled": {
                                "type": "boolean"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "API-token principals are not allowed here."
          },
          "404": {
            "description": "Unknown, expired, or already-answered request."
          }
        }
      },
      "post": {
        "operationId": "answerOauthConsent",
        "tags": [
          "OAuth (MCP Connector)"
        ],
        "summary": "Approve or deny an authorization request",
        "description": "Approval picks the organization and the exact scopes (a subset of the request's scopes when it named any; never `admin`), verifies membership and the organization's MCP entitlement, and mints the single-use authorization code. The response carries the `redirect_url` the page navigates to — SignFlow never echoes the code anywhere else. Denial consumes the request and redirects with `error=access_denied`.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "parameters": [
          {
            "name": "requestId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "approve"
                ],
                "properties": {
                  "approve": {
                    "type": "boolean"
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Where to send the browser next.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "redirect_url": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing organization, empty or non-grantable scopes."
          },
          "402": {
            "description": "`feature_locked` — the organization's plan does not include MCP access."
          },
          "403": {
            "description": "Not a member of that organization, or an API-token principal."
          },
          "404": {
            "description": "Unknown, expired, or already-answered request."
          },
          "409": {
            "description": "The request was answered concurrently."
          }
        }
      }
    },
    "/api/v1/oauth/grants": {
      "get": {
        "operationId": "listOauthGrants",
        "tags": [
          "OAuth (MCP Connector)"
        ],
        "summary": "List my MCP connections",
        "description": "The signed-in user's active grants — which organization each one is bound to, its scopes, and when it was last used. Powers the Settings → Connected apps list. Browser session only.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Active grants, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "organization_id": {
                            "type": "string"
                          },
                          "organization_name": {
                            "type": "string"
                          },
                          "scopes": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "last_used_at": {
                            "type": "string",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "API-token principals are not allowed here."
          }
        }
      }
    },
    "/api/v1/oauth/grants/{grantId}": {
      "delete": {
        "operationId": "revokeOauthGrant",
        "tags": [
          "OAuth (MCP Connector)"
        ],
        "summary": "Revoke one of my MCP connections",
        "description": "Immediately invalidates the grant's access and refresh tokens. Only the user who approved a grant can revoke it here (the client can also revoke via `/oauth/revoke`). Browser session only.",
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "parameters": [
          {
            "name": "grantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "API-token principals are not allowed here."
          },
          "404": {
            "description": "No such active grant owned by you."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiToken": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "An organisation-bound API token, passed raw with no `Bearer` prefix: `Authorization: sf_...`. Create one on the API Keys page. Scopes are declared per operation."
      },
      "SessionAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "A signed-in user's session JWT, from the OAuth/SSO flows under `/auth`. Also accepted as the `auth_token` cookie. Not scope-limited — the user's own organisation role and granular permissions apply."
      },
      "SigningToken": {
        "type": "apiKey",
        "in": "query",
        "name": "token",
        "description": "A per-signer signing token, minted when the submission is created and carried in the signing link. Authorises only that signer's ceremony endpoints. Some ceremony endpoints take it in the JSON body instead of the query string; those declare it there."
      }
    },
    "parameters": {
      "OrgIdPath": {
        "name": "orgId",
        "in": "path",
        "required": true,
        "description": "Organisation id. An API token may only address the organisation it is bound to.",
        "schema": {
          "type": "string"
        }
      },
      "OrganizationIdQuery": {
        "name": "organization_id",
        "in": "query",
        "required": false,
        "description": "Restrict to one organisation. Ignored for API tokens, which are already bound to an organisation; for session callers it is checked against membership.",
        "schema": {
          "type": "string"
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Maximum rows to return.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "Offset": {
        "name": "offset",
        "in": "query",
        "required": false,
        "description": "Rows to skip, for pagination.",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "default": 0
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Malformed request — invalid JSON, a missing required field, or a value that failed validation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "No credential, or one that is invalid, expired or revoked.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Authenticated, but not allowed: the token lacks the required scope, the credential belongs to another organisation, or the caller's role/permissions do not cover this action.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ScopeError"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "The organisation's plan does not include this feature, or it is out of credits.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PlanGateError"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such resource, or it is not visible to this credential.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "The resource is in a state that forbids this change — for example a submission that has already completed, been voided or declined.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RequiredMissing": {
        "description": "A module of the flow leaves a required field empty (`required_missing`). `issues` names each one by module (`moduleId`) and field path (`config.<name>` or `map.<name>`); `truncated`, when present, counts the ones left out. Save and turning a flow on refuse this; a draft write never does, and a flow that is already on keeps running.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error",
                "code",
                "issues"
              ],
              "properties": {
                "error": {
                  "type": "string"
                },
                "code": {
                  "type": "string",
                  "enum": [
                    "required_missing"
                  ]
                },
                "issues": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "code",
                      "message",
                      "moduleId",
                      "field"
                    ],
                    "properties": {
                      "code": {
                        "type": "string",
                        "enum": [
                          "required_missing"
                        ]
                      },
                      "message": {
                        "type": "string",
                        "description": "English, for logs; the field's own label, never a value."
                      },
                      "moduleId": {
                        "type": "integer"
                      },
                      "field": {
                        "type": "string",
                        "description": "`config.<name>` or `map.<name>`."
                      }
                    }
                  }
                },
                "truncated": {
                  "type": "integer",
                  "description": "How many more were left out past the first 200."
                }
              }
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded for this endpoint. See the operation's `x-rate-limit`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServerError": {
        "description": "Unexpected server error. The `X-Request-Id` response header identifies the request in the logs.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "FlowPath": {
        "type": "object",
        "required": [
          "apps",
          "fork",
          "more"
        ],
        "description": "The apps along the flow's first route, as a mini map: app ids in order, at most one fork, and how many modules are beyond.",
        "properties": {
          "apps": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "App ids in flow order, one per run of modules of the same app; at most 4 before a fork. With a fork, `apps.slice(fork.at)` is its first route."
          },
          "fork": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "at",
              "apps"
            ],
            "properties": {
              "at": {
                "type": "integer",
                "description": "The index in `apps` where the first route starts."
              },
              "apps": {
                "type": "array",
                "items": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "description": "The next routes' apps, at most 2 routes of 2 apps."
              }
            }
          },
          "more": {
            "type": "integer",
            "description": "Modules the map does not reach."
          }
        }
      },
      "FlowRunsDay": {
        "type": "object",
        "required": [
          "date",
          "runs",
          "errors",
          "warnings"
        ],
        "properties": {
          "date": {
            "type": "string",
            "format": "date",
            "description": "A calendar day in the organization's time zone."
          },
          "runs": {
            "type": "integer",
            "description": "Runs that started that day, safe tests and empty checks left out."
          },
          "errors": {
            "type": "integer"
          },
          "warnings": {
            "type": "integer"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message."
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable code, where one applies."
          }
        },
        "example": {
          "error": "Submission not found"
        }
      },
      "ScopeError": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Error"
          },
          {
            "type": "object",
            "properties": {
              "message": {
                "type": "string"
              },
              "required_scopes": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Scope"
                },
                "description": "Any one of these scopes (or `admin`) would have satisfied the check."
              }
            }
          }
        ],
        "example": {
          "error": "Insufficient permissions",
          "message": "This action requires one of the following scopes: templates:write",
          "required_scopes": [
            "templates:write"
          ]
        }
      },
      "PlanGateError": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Error"
          },
          {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "feature_locked",
                  "insufficient_credits",
                  "limit_reached"
                ]
              },
              "required_tier": {
                "type": "string",
                "description": "The lowest plan that includes this feature.",
                "example": "pro"
              }
            }
          }
        ],
        "example": {
          "error": "API access is not included in your plan",
          "code": "feature_locked",
          "required_tier": "pro"
        }
      },
      "SuccessResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "message": {
            "type": "string"
          }
        }
      },
      "Scope": {
        "type": "string",
        "enum": [
          "templates:read",
          "templates:write",
          "submissions:read",
          "submissions:write",
          "webhooks:read",
          "webhooks:write",
          "organization:read",
          "audit:read",
          "flows:read",
          "flows:write",
          "flows:run",
          "integrations:read",
          "integrations:write",
          "admin"
        ]
      },
      "FieldType": {
        "type": "string",
        "description": "The stored field type. Eleven types are persisted in the database column; the richer editor types are stored as one of these plus an `extended_field_type` discriminator (see `Field.extended_field_type`).",
        "enum": [
          "signature",
          "initials",
          "date",
          "text",
          "textarea",
          "email",
          "phone",
          "number",
          "checkbox",
          "dropdown",
          "attachment"
        ]
      },
      "ExtendedFieldType": {
        "type": "string",
        "description": "The editor-level field type. Where it has no database equivalent it is stored alongside a base `field_type` — `id_number`, `fullname` and `address` are stored as `text`; `currency` as `number`; `yes_no` as `checkbox`; `radio_group` as `dropdown`; `image` as `attachment`; `signing_date` as `date`. The display-only types (`header`, `helper_text`, `divider`, `link`, `html`) render on the document and collect no value.",
        "enum": [
          "signature",
          "initials",
          "date",
          "text",
          "textarea",
          "email",
          "phone",
          "number",
          "checkbox",
          "dropdown",
          "attachment",
          "fullname",
          "id_number",
          "signing_date",
          "yes_no",
          "address",
          "url",
          "image",
          "time",
          "currency",
          "radio_group",
          "header",
          "helper_text",
          "divider",
          "link",
          "html"
        ]
      },
      "SubmissionStatus": {
        "type": "string",
        "description": "`pending` until a signer opens it, then `viewed`; `signed` once a signer has signed and others still have to; then one of the terminal states (`completed`, `declined`, `voided`, `expired`). Terminal submissions are immutable: `PATCH` returns 409. A request created with `hold: true` is `draft` (shown as \"Draft (held for review)\") until it is released or voided: nobody is invited and its signing links do not work. `draft` is used for held requests only.",
        "enum": [
          "pending",
          "draft",
          "viewed",
          "signed",
          "completed",
          "declined",
          "voided",
          "expired"
        ]
      },
      "SignerStatus": {
        "type": "string",
        "enum": [
          "pending",
          "viewed",
          "signed",
          "declined"
        ]
      },
      "SigningMode": {
        "type": "string",
        "description": "`sequential` invites signer *n+1* only once *n* has signed; `parallel` invites everyone at once.",
        "enum": [
          "sequential",
          "parallel"
        ]
      },
      "Template": {
        "type": "object",
        "description": "A reusable document. Timestamps are ISO-8601 UTC; booleans are stored as 0/1 integers.",
        "properties": {
          "id": {
            "type": "string"
          },
          "user_id": {
            "type": "string",
            "description": "The user who created it."
          },
          "organization_id": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "pdf_url": {
            "type": "string",
            "nullable": true,
            "description": "The source PDF."
          },
          "pdf_hash": {
            "type": "string",
            "nullable": true,
            "description": "SHA-256 of the source PDF, recorded when it was attached."
          },
          "append_pdf_url": {
            "type": "string",
            "nullable": true,
            "description": "A display-only PDF appended after the document and before the audit trail."
          },
          "append_pdf_name": {
            "type": "string",
            "nullable": true
          },
          "append_pdf_hash": {
            "type": "string",
            "nullable": true
          },
          "language": {
            "type": "string",
            "description": "Ceremony language, e.g. `en` or `he`.",
            "example": "en"
          },
          "logo_url": {
            "type": "string",
            "nullable": true
          },
          "allow_download": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "allow_print": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "preview_mode": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Controls the review step *after* signing. 0 makes the form terminal."
          },
          "fill_at_first_stage": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Show a document-fill landing step before the ceremony."
          },
          "save_for_later": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "expiration_days": {
            "type": "integer",
            "description": "Days from send until a submission expires."
          },
          "redirect_url": {
            "type": "string",
            "nullable": true,
            "description": "Where the signer lands after signing. Must be same-origin with the platform or the organisation's verified custom domain."
          },
          "success_message": {
            "type": "string",
            "nullable": true
          },
          "include_audit_trail": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Append the audit-trail PDF to the signed document."
          },
          "is_active": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "signing_mode": {
            "$ref": "#/components/schemas/SigningMode"
          },
          "webhook_url": {
            "type": "string",
            "nullable": true,
            "description": "A per-template webhook target, in addition to the organisation's endpoints."
          },
          "webhook_events": {
            "type": "string",
            "nullable": true,
            "description": "JSON array of event names, as stored."
          },
          "show_signing_stamp": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "show_completion_footer": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "auto_detect_fields": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "require_approval": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "signing_password_enabled": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Gate the ceremony behind a password. Requires the Business plan."
          },
          "signing_password_mode": {
            "type": "string",
            "nullable": true,
            "enum": [
              "auto",
              "fixed",
              null
            ],
            "description": "`auto` generates a password per submission and returns it once on create; `fixed` uses one password for every submission of this template."
          },
          "tax_compliant": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Israeli Tax Authority preset. Forces signer OTP, audit trail and approval on, and AI detection off."
          },
          "require_signer_otp": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "otp_channel": {
            "type": "string",
            "nullable": true,
            "enum": [
              "email",
              null
            ]
          },
          "preshared_knowledge_type": {
            "type": "string",
            "nullable": true,
            "description": "Extra identity challenge, e.g. `none` or `id_number`."
          },
          "retention_years": {
            "type": "integer",
            "description": "How long signed documents are retained."
          },
          "payment_enabled": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Require the signer to pay (or save a card) before submitting."
          },
          "payment_amount_agorot": {
            "type": "integer",
            "nullable": true,
            "description": "Amount in the currency's minor unit (agorot for ILS)."
          },
          "payment_currency": {
            "type": "string",
            "example": "ILS"
          },
          "payment_description": {
            "type": "string",
            "nullable": true
          },
          "payment_action_type": {
            "type": "string",
            "nullable": true,
            "enum": [
              "J4",
              "J5",
              "J2",
              null
            ],
            "description": "`J4` charge, `J5` authorisation hold, `J2` card-on-file with no charge."
          },
          "detect_field_types": {
            "type": "string",
            "nullable": true,
            "description": "JSON array limiting which field types automatic detection may produce."
          },
          "field_count": {
            "type": "integer",
            "description": "Present on list responses."
          },
          "submission_count": {
            "type": "integer",
            "description": "Present on list responses."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TemplateWithFields": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Template"
          },
          {
            "type": "object",
            "properties": {
              "fields": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Field"
                }
              }
            }
          }
        ]
      },
      "TemplateInput": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "pdf_url": {
            "type": "string",
            "description": "URL of an already-uploaded PDF. Upload one first with `POST /api/v1/storage/upload`."
          },
          "pdf_data": {
            "type": "string",
            "description": "Base64-encoded PDF, as an alternative to `pdf_url`."
          },
          "organization_id": {
            "type": "string",
            "description": "Ignored for API tokens (the token's organisation wins). Session callers must be a member."
          },
          "language": {
            "type": "string",
            "default": "en"
          },
          "signing_mode": {
            "$ref": "#/components/schemas/SigningMode"
          },
          "expiration_days": {
            "type": "integer",
            "default": 30
          },
          "redirect_url": {
            "type": "string"
          },
          "success_message": {
            "type": "string"
          },
          "allow_download": {
            "type": "boolean"
          },
          "allow_print": {
            "type": "boolean"
          },
          "preview_mode": {
            "type": "boolean"
          },
          "fill_at_first_stage": {
            "type": "boolean"
          },
          "save_for_later": {
            "type": "boolean"
          },
          "include_audit_trail": {
            "type": "boolean"
          },
          "show_signing_stamp": {
            "type": "boolean"
          },
          "show_completion_footer": {
            "type": "boolean"
          },
          "auto_detect_fields": {
            "type": "boolean"
          },
          "require_approval": {
            "type": "boolean",
            "default": true
          },
          "require_signer_otp": {
            "type": "boolean"
          },
          "tax_compliant": {
            "type": "boolean",
            "description": "Locks the compliance preset on; other flags it governs are forced."
          },
          "preshared_knowledge_type": {
            "type": "string"
          },
          "retention_years": {
            "type": "integer",
            "minimum": 1
          },
          "webhook_url": {
            "type": "string"
          },
          "webhook_events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "detect_field_types": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExtendedFieldType"
            }
          },
          "signing_password_enabled": {
            "type": "boolean",
            "description": "Business plan and above."
          },
          "signing_password_mode": {
            "type": "string",
            "enum": [
              "auto",
              "fixed"
            ]
          },
          "signing_password_value": {
            "type": "string",
            "description": "Required when mode is `fixed`. Stored encrypted and never returned."
          },
          "payment_enabled": {
            "type": "boolean"
          },
          "payment_amount_agorot": {
            "type": "integer",
            "minimum": 1
          },
          "payment_currency": {
            "type": "string",
            "default": "ILS"
          },
          "payment_description": {
            "type": "string"
          },
          "payment_action_type": {
            "type": "string",
            "enum": [
              "J4",
              "J5",
              "J2"
            ]
          },
          "is_active": {
            "type": "boolean"
          }
        }
      },
      "Field": {
        "type": "object",
        "description": "One field on a template. Geometry is in PDF points, with `x`/`y` measured from the top-left of the page.",
        "properties": {
          "id": {
            "type": "string"
          },
          "template_id": {
            "type": "string"
          },
          "field_key": {
            "type": "string",
            "description": "The stable key this field's value is stored under. Never renamed by label changes."
          },
          "field_type": {
            "$ref": "#/components/schemas/FieldType"
          },
          "extended_field_type": {
            "$ref": "#/components/schemas/ExtendedFieldType"
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "placeholder": {
            "type": "string",
            "nullable": true
          },
          "default_value": {
            "type": "string",
            "nullable": true,
            "description": "Supports `@`-mappings such as `@first_name`, resolved per signer at send time."
          },
          "required": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "read_only": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "is_fallback": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Added by the fallback pass, which only runs when detection found nothing and the PDF has no fillable fields."
          },
          "assigned_signer": {
            "type": "string",
            "nullable": true,
            "description": "The signer role that fills this field. `null` means anyone/unassigned."
          },
          "options": {
            "type": "string",
            "nullable": true,
            "description": "JSON array of choices, for `dropdown` and `radio_group`."
          },
          "page": {
            "type": "integer",
            "description": "1-based page number."
          },
          "x": {
            "type": "number"
          },
          "y": {
            "type": "number"
          },
          "width": {
            "type": "number"
          },
          "height": {
            "type": "number"
          },
          "rotation": {
            "type": "number"
          },
          "opacity": {
            "type": "number"
          },
          "font_family": {
            "type": "string"
          },
          "font_size": {
            "type": "number"
          },
          "font_weight": {
            "type": "string"
          },
          "font_style": {
            "type": "string"
          },
          "text_decoration": {
            "type": "string"
          },
          "text_align": {
            "type": "string",
            "enum": [
              "left",
              "center",
              "right"
            ]
          },
          "line_height": {
            "type": "number"
          },
          "letter_spacing": {
            "type": "number"
          },
          "text_direction": {
            "type": "string",
            "nullable": true,
            "enum": [
              "auto",
              "rtl",
              "ltr",
              null
            ],
            "description": "`auto` detects direction per glyph, which is what mixed Hebrew/Latin documents need."
          },
          "text_wrap": {
            "type": "integer",
            "nullable": true,
            "enum": [
              0,
              1,
              null
            ],
            "description": "`null` infers from the field type."
          },
          "text_color": {
            "type": "string"
          },
          "background_color": {
            "type": "string",
            "nullable": true
          },
          "border_color": {
            "type": "string",
            "nullable": true
          },
          "validation_regex": {
            "type": "string",
            "nullable": true
          },
          "validation_pattern": {
            "type": "string",
            "nullable": true
          },
          "validation_message": {
            "type": "string",
            "nullable": true
          },
          "min_length": {
            "type": "integer",
            "nullable": true
          },
          "max_length": {
            "type": "integer",
            "nullable": true
          },
          "api_field_name": {
            "type": "string",
            "nullable": true,
            "description": "An alias used only when rendering webhook payloads. Storage, prefills and inbound writes always key by `field_key`."
          },
          "unique_search_key": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "store_in_database": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "display_on_document": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "repeat_on_all_pages": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          }
        }
      },
      "FieldInput": {
        "type": "object",
        "required": [
          "field_type",
          "page",
          "x",
          "y",
          "width",
          "height"
        ],
        "description": "A field as supplied to `PUT /api/v1/templates/{id}/fields`. Omitted styling falls back to the template default.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Include to keep a field's identity across a replace; omit to create."
          },
          "field_key": {
            "type": "string",
            "description": "Generated from the label when omitted."
          },
          "field_type": {
            "$ref": "#/components/schemas/FieldType"
          },
          "extended_field_type": {
            "$ref": "#/components/schemas/ExtendedFieldType"
          },
          "label": {
            "type": "string"
          },
          "placeholder": {
            "type": "string"
          },
          "default_value": {
            "type": "string"
          },
          "required": {
            "type": "boolean"
          },
          "read_only": {
            "type": "boolean"
          },
          "assigned_signer": {
            "type": "string"
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "page": {
            "type": "integer",
            "minimum": 1
          },
          "x": {
            "type": "number"
          },
          "y": {
            "type": "number"
          },
          "width": {
            "type": "number"
          },
          "height": {
            "type": "number"
          },
          "rotation": {
            "type": "number"
          },
          "opacity": {
            "type": "number"
          },
          "font_family": {
            "type": "string"
          },
          "font_size": {
            "type": "number"
          },
          "font_weight": {
            "type": "string"
          },
          "font_style": {
            "type": "string"
          },
          "text_decoration": {
            "type": "string"
          },
          "text_align": {
            "type": "string",
            "enum": [
              "left",
              "center",
              "right"
            ]
          },
          "line_height": {
            "type": "number"
          },
          "letter_spacing": {
            "type": "number"
          },
          "text_direction": {
            "type": "string",
            "enum": [
              "auto",
              "rtl",
              "ltr"
            ]
          },
          "text_wrap": {
            "type": "boolean"
          },
          "text_color": {
            "type": "string"
          },
          "background_color": {
            "type": "string"
          },
          "border_color": {
            "type": "string"
          },
          "validation_regex": {
            "type": "string"
          },
          "validation_pattern": {
            "type": "string"
          },
          "validation_message": {
            "type": "string"
          },
          "min_length": {
            "type": "integer"
          },
          "max_length": {
            "type": "integer"
          },
          "api_field_name": {
            "type": "string"
          },
          "unique_search_key": {
            "type": "boolean"
          },
          "store_in_database": {
            "type": "boolean"
          },
          "display_on_document": {
            "type": "boolean"
          },
          "repeat_on_all_pages": {
            "type": "boolean"
          }
        }
      },
      "Submission": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "template_id": {
            "type": "string"
          },
          "template_name": {
            "type": "string",
            "description": "Joined from the template."
          },
          "user_id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/SubmissionStatus"
          },
          "fields_data": {
            "type": "object",
            "additionalProperties": true,
            "description": "Field values keyed by `field_key`. Parsed from storage before it is returned."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Whatever you attached at creation. SignFlow never interprets it."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "pdf_url": {
            "type": "string",
            "nullable": true,
            "description": "The document as sent."
          },
          "signed_pdf_url": {
            "type": "string",
            "nullable": true,
            "description": "Populated once the signed PDF has been generated."
          },
          "webhook_url": {
            "type": "string",
            "nullable": true
          },
          "redirect_url": {
            "type": "string",
            "nullable": true
          },
          "require_signer_otp": {
            "type": "integer",
            "nullable": true,
            "enum": [
              0,
              1,
              null
            ]
          },
          "otp_channel": {
            "type": "string",
            "nullable": true
          },
          "retention_years": {
            "type": "integer",
            "nullable": true
          },
          "payment_required": {
            "type": "integer",
            "nullable": true,
            "enum": [
              0,
              1,
              null
            ]
          },
          "payment_amount_agorot": {
            "type": "integer",
            "nullable": true
          },
          "payment_currency": {
            "type": "string",
            "nullable": true
          },
          "payment_description": {
            "type": "string",
            "nullable": true
          },
          "payment_action_type": {
            "type": "string",
            "nullable": true,
            "enum": [
              "J4",
              "J5",
              "J2",
              null
            ]
          },
          "created_ip": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "signing_password_set": {
            "type": "boolean",
            "description": "Whether this submission is gated behind a signing password. The password itself — and the hash and salt behind it — are never returned."
          }
        }
      },
      "SubmissionWithSigners": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Submission"
          },
          {
            "type": "object",
            "properties": {
              "signers": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Signer"
                }
              }
            }
          }
        ]
      },
      "SubmissionDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SubmissionWithSigners"
          },
          {
            "type": "object",
            "properties": {
              "audit_log": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AuditEvent"
                }
              },
              "fields": {
                "type": "array",
                "description": "The submission's fields in document order. `fields_data` is keyed by `field_key`; use this list to name each value.",
                "items": {
                  "type": "object",
                  "properties": {
                    "field_key": {
                      "type": "string",
                      "description": "The key this field's value has in `fields_data`."
                    },
                    "label": {
                      "type": "string",
                      "nullable": true
                    },
                    "field_type": {
                      "type": "string"
                    },
                    "api_field_name": {
                      "type": "string",
                      "nullable": true
                    },
                    "display_on_document": {
                      "type": "boolean",
                      "description": "False for a data-only field, which is never printed on the PDF."
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "Signer": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "submission_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "role": {
            "type": "string",
            "nullable": true,
            "description": "Matches a template field's `assigned_signer`."
          },
          "signing_order": {
            "type": "integer",
            "description": "1-based. Drives the order in a `sequential` flow."
          },
          "status": {
            "$ref": "#/components/schemas/SignerStatus"
          },
          "signing_token": {
            "type": "string",
            "description": "This signer's secret. Anyone holding it can sign as them — treat it as a credential and never log or forward it."
          },
          "signing_url": {
            "type": "string",
            "description": "The ready-made link, returned when the submission is created or a signer is resent."
          },
          "viewed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "signed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "declined_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "decline_reason": {
            "type": "string",
            "nullable": true
          },
          "downloaded_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "read_confirmed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "otp_verified": {
            "type": "integer",
            "nullable": true,
            "enum": [
              0,
              1,
              null
            ]
          },
          "ip_address": {
            "type": "string",
            "nullable": true
          },
          "user_agent": {
            "type": "string",
            "nullable": true
          },
          "signature_data": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "The captured signature and per-signer field values. Parsed before it is returned."
          }
        }
      },
      "SignerInput": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Required for SignFlow to send the invitation. Omit it and you get a signing link back to deliver yourself."
          },
          "phone": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "description": "Binds this signer to the template fields assigned to that role."
          },
          "signing_order": {
            "type": "integer",
            "minimum": 1
          },
          "field_values": {
            "type": "object",
            "additionalProperties": true,
            "description": "Values prefilled for this signer only, keyed by `field_key`."
          },
          "identity_verified_method": {
            "type": "string",
            "description": "Record that you verified this signer's identity out of band."
          },
          "identity_verified_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateSubmissionInput": {
        "type": "object",
        "required": [
          "template_id",
          "signers"
        ],
        "properties": {
          "template_id": {
            "type": "string"
          },
          "signers": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/SignerInput"
            }
          },
          "name": {
            "type": "string",
            "maxLength": 200,
            "description": "Defaults to the template's name."
          },
          "organization_id": {
            "type": "string",
            "description": "Only consulted for an organisation-less template. Ignored for API tokens."
          },
          "fields_data": {
            "description": "Values prefilled for every signer, keyed by `field_key`. Also accepts an array of `{ key, value }` pairs. Fields whose target is read-only are resolved server-side and printed on the document.",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": true
              },
              {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "key",
                    "value"
                  ],
                  "properties": {
                    "key": {
                      "type": "string"
                    },
                    "value": {}
                  }
                }
              }
            ]
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Opaque data echoed back on reads and in webhook payloads."
          },
          "pdf_url": {
            "type": "string",
            "description": "Override the template's PDF for this one submission."
          },
          "pdf_data": {
            "type": "string",
            "description": "Base64 PDF, as an alternative to `pdf_url`."
          },
          "webhook_url": {
            "type": "string",
            "description": "A per-submission webhook target, validated against the SSRF guard."
          },
          "redirect_url": {
            "type": "string",
            "description": "Must be same-origin with the platform or the organisation's verified custom domain."
          },
          "decline_button_text": {
            "type": "string"
          },
          "auto_detect_tags": {
            "type": "boolean",
            "description": "Run field detection against this submission's PDF. Accepts the string `\"true\"` as well."
          },
          "detect_field_types": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExtendedFieldType"
            },
            "description": "Limit what detection may produce."
          },
          "hold": {
            "type": "boolean",
            "default": false,
            "description": "Hold the request for review: its status is `draft` until it is released or voided, nobody is e-mailed, signing links do not work, and the response carries a `preview_url` that shows the document with its signature boxes and a PDF download. Send the invitations with `POST /api/v1/submissions/{id}/release`. Needs `pdf_data`, at least one signer and an organisation; not available with a signing password, signer payment, or a template that appends pages."
          },
          "payment": {
            "type": "object",
            "description": "Override the template's signer-payment gate for this submission.",
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "amount_agorot": {
                "type": "integer",
                "minimum": 1
              },
              "description": {
                "type": "string"
              }
            }
          }
        }
      },
      "CreateSubmissionResponse": {
        "type": "object",
        "description": "What creation returns. `signing_url` mirrors the first signer's link; with no signers it is a token-less link whose first visitor becomes an anonymous signer.",
        "properties": {
          "id": {
            "type": "string"
          },
          "template_id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/SubmissionStatus"
          },
          "signing_mode": {
            "$ref": "#/components/schemas/SigningMode"
          },
          "pdf_url": {
            "type": "string",
            "nullable": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "payment_required": {
            "type": "boolean"
          },
          "payment_amount_agorot": {
            "type": "integer",
            "nullable": true
          },
          "signing_url": {
            "type": "string"
          },
          "signers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Signer"
            }
          },
          "pdf_sha256": {
            "type": "string",
            "description": "Held requests only: SHA-256 (hex) of the stored document. Pass it to release."
          },
          "preview_url": {
            "type": "string",
            "description": "Held requests only: read-only preview link (document, signature boxes, PDF download). The token is in the URL fragment (#t=…), which is never sent to a server. Treat the link like a password; it works until the request ends."
          },
          "preview_token": {
            "type": "string",
            "description": "Held requests only: the token inside `preview_url` (after #t=). Returned once; SignFlow keeps only its hash. Send it in the body of POST /signing/preview/{id}."
          },
          "signing_password": {
            "type": "string",
            "description": "Present only when the template generates a password per submission. Returned once, here, and never again."
          }
        }
      },
      "AuditEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "submission_id": {
            "type": "string"
          },
          "signer_id": {
            "type": "string",
            "nullable": true
          },
          "event_type": {
            "type": "string",
            "example": "signer.signed"
          },
          "event_data": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "ip_address": {
            "type": "string",
            "nullable": true
          },
          "user_agent": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "description": "Every event SignFlow emits. The three payment events fire only for organisations collecting signer payments; `submission.card_saved` is deliberately separate from `submission.payment_completed` because nothing was charged, so payment reconciliation must not treat it as money.",
        "enum": [
          "submission.created",
          "submission.sent",
          "submission.signed",
          "submission.completed",
          "submission.declined",
          "submission.voided",
          "submission.expired",
          "submission.payment_completed",
          "submission.card_saved",
          "submission.payment_failed",
          "signer.opened",
          "signer.viewed",
          "signer.signed",
          "signer.declined",
          "signer.reminder_sent",
          "document.generated",
          "document.downloaded"
        ]
      },
      "WebhookHeaders": {
        "type": "object",
        "description": "Custom HTTP headers sent with every delivery to this endpoint — first attempt, retries, and `POST /api/v1/webhooks/test` when called with `webhook_id`. The typical use is a header a WAF or Cloud Armor rule can match, because SignFlow delivers from Cloudflare Workers and has no stable egress IP to allow-list.\n\nRules (each violation is a 400 with a specific message):\n- at most 10 headers;\n- names match `^[A-Za-z0-9-]{1,64}$`;\n- values are strings of 1–1024 characters with no CR or LF;\n- names are compared case-insensitively for duplicates and for the reserved list, then sent exactly as written.\n\nReserved names, rejected rather than silently dropped: `host, content-type, content-length, connection, transfer-encoding, x-leonai-signature, user-agent, x-leonai-event, x-leonai-delivery, x-leonai-timestamp, x-leonai-retry`. Every `X-LeonAI-*` header SignFlow itself sets is written AFTER your headers, so a custom header can never override the signature. The `X-LeonAI-` prefix as a whole is not reserved — `X-LeonAI-Origin` is fine.\n\nValues are credentials: stored encrypted, never returned in full (reads are masked), and never written to the audit log — only the header names are.",
        "maxProperties": 10,
        "additionalProperties": {
          "type": "string",
          "minLength": 1,
          "maxLength": 1024
        },
        "example": {
          "X-LeonAI-Origin": "9f2c…"
        }
      },
      "Webhook": {
        "type": "object",
        "description": "An organisation webhook endpoint.",
        "properties": {
          "id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "secret": {
            "type": "string",
            "nullable": true,
            "description": "Masked as bullets on read. The real value is returned once, in the create response."
          },
          "headers": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WebhookHeaders"
              }
            ],
            "description": "Masked on every read, including the create response: each value is six bullets plus its last two characters (bullets alone for values under 8 characters). The full value is never returned. `{}` when none are set."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "is_active": {
            "type": "boolean"
          },
          "template_id": {
            "type": "string",
            "nullable": true,
            "description": "When set, the endpoint only fires for submissions of that template."
          },
          "source": {
            "type": "string",
            "nullable": true,
            "description": "Where the endpoint came from — e.g. an integration platform that registered it."
          },
          "created_by_token_id": {
            "type": "string",
            "nullable": true,
            "description": "Set when an API token registered the endpoint rather than a person."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateWebhookInput": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Must be HTTPS and publicly resolvable. Private, loopback and link-local addresses are rejected outright (SSRF)."
          },
          "name": {
            "type": "string",
            "description": "Defaults to `Webhook`."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            },
            "description": "Defaults to `[\"submission.completed\"]`."
          },
          "secret": {
            "type": "string",
            "description": "Supply your own signing secret, or omit it and one is generated. Either way it is returned in full exactly once, in the create response."
          },
          "headers": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WebhookHeaders"
              }
            ],
            "description": "The full map. Not accepted together with `template_id` — a template-scoped registration is stored on the template and has nowhere to keep an encrypted map."
          },
          "is_active": {
            "type": "boolean",
            "default": true
          },
          "template_id": {
            "type": "string",
            "description": "Restrict the endpoint to one template. Must belong to the same organisation."
          }
        }
      },
      "WebhookPayload": {
        "type": "object",
        "description": "The body POSTed to a webhook endpoint. Verify `X-LeonAI-Signature` before trusting it.",
        "properties": {
          "event": {
            "$ref": "#/components/schemas/WebhookEventType"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "type": "object",
            "properties": {
              "submission_id": {
                "type": "string"
              },
              "submission_name": {
                "type": "string"
              },
              "template_id": {
                "type": "string"
              },
              "template_name": {
                "type": "string"
              },
              "status": {
                "$ref": "#/components/schemas/SubmissionStatus"
              },
              "signer": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "status": {
                    "$ref": "#/components/schemas/SignerStatus"
                  }
                }
              },
              "fields_data": {
                "type": "object",
                "additionalProperties": true,
                "description": "Keyed by `api_field_name` where a field defines one, otherwise by `field_key`."
              },
              "metadata": {
                "type": "object",
                "additionalProperties": true
              },
              "signed_pdf_url": {
                "type": "string"
              },
              "payment": {
                "type": "object",
                "description": "Present on the payment events. `amount_agorot` is null for `submission.card_saved`.",
                "properties": {
                  "provider": {
                    "type": "string"
                  },
                  "action_type": {
                    "type": "string",
                    "enum": [
                      "J4",
                      "J5",
                      "J2"
                    ]
                  },
                  "amount_agorot": {
                    "type": "integer",
                    "nullable": true
                  },
                  "currency": {
                    "type": "string"
                  },
                  "card_saved": {
                    "type": "boolean"
                  },
                  "reference": {
                    "type": "string"
                  }
                }
              },
              "failure": {
                "type": "object",
                "description": "Present on `submission.payment_failed`.",
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "description": "One delivery attempt. Every outcome is recorded, including the ones that never left the queue.",
        "properties": {
          "id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "webhook_id": {
            "type": "string"
          },
          "submission_id": {
            "type": "string",
            "nullable": true,
            "description": "Null for events that are not about a submission."
          },
          "webhook_url": {
            "type": "string",
            "description": "The URL as it was at the time of the attempt, not as it is now."
          },
          "event_type": {
            "$ref": "#/components/schemas/WebhookEventType"
          },
          "payload": {
            "type": "object",
            "additionalProperties": true,
            "description": "The body that was sent, parsed back from storage."
          },
          "status_code": {
            "type": "integer",
            "nullable": true,
            "description": "Null when the request never got a response."
          },
          "response_body": {
            "type": "string",
            "nullable": true,
            "description": "Truncated."
          },
          "response_headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "nullable": true
          },
          "duration_ms": {
            "type": "integer",
            "nullable": true
          },
          "attempt_number": {
            "type": "integer",
            "description": "1 for the first attempt."
          },
          "max_retries": {
            "type": "integer"
          },
          "next_retry_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the retry cron will try again. Null when there is nothing more to do."
          },
          "delivery_status": {
            "type": "string",
            "description": "e.g. `success`, `failed`, `pending`, `cancelled`."
          },
          "error_message": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "delivered_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "ApiToken": {
        "type": "object",
        "description": "An API token as it is read back. The secret itself is shown once, at creation, and never again.",
        "properties": {
          "id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string",
            "description": "The single organisation this token can act in."
          },
          "user_id": {
            "type": "string",
            "description": "The member who created it; the token acts as this user."
          },
          "name": {
            "type": "string"
          },
          "token_prefix": {
            "type": "string",
            "description": "The token's last four characters — enough to tell two tokens apart in a list."
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            }
          },
          "is_active": {
            "type": "boolean",
            "description": "False once revoked. A revoked token is kept so the audit log still resolves it."
          },
          "is_own": {
            "type": "boolean",
            "description": "True when the caller created this token. Admins and owners see every token in the organisation; members see only their own."
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Null means it does not expire."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by_name": {
            "type": "string",
            "nullable": true,
            "description": "Only present for admins and owners listing the whole organisation."
          },
          "created_by_email": {
            "type": "string",
            "nullable": true,
            "description": "Only present for admins and owners listing the whole organisation."
          }
        }
      },
      "Organization": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string",
            "description": "Unique across the platform; appears in URLs. Lowercase letters, digits and hyphens only."
          },
          "logo_url": {
            "type": "string",
            "nullable": true
          },
          "square_icon_url": {
            "type": "string",
            "nullable": true,
            "description": "Square crop, for favicons and app icons."
          },
          "settings": {
            "allOf": [
              {
                "$ref": "#/components/schemas/OrganizationSettings"
              }
            ],
            "nullable": true,
            "description": "Parsed from storage. Null when never configured."
          },
          "agency_id": {
            "type": "string",
            "nullable": true,
            "description": "Set when the organisation belongs to a white-label agency rather than to SignFlow directly."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Member": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "user_id": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "member"
            ]
          },
          "email": {
            "type": "string",
            "description": "The member's email address."
          },
          "profile": {
            "type": "object",
            "nullable": true,
            "properties": {
              "full_name": {
                "type": "string",
                "nullable": true
              },
              "avatar_url": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "invited_by": {
            "type": "string",
            "nullable": true
          },
          "joined_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Invite": {
        "type": "object",
        "description": "A pending invitation. The token is never returned — it only ever reaches the recipient, by email.",
        "properties": {
          "id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "member"
            ]
          },
          "invited_by": {
            "type": "string",
            "nullable": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "accepted_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Role": {
        "type": "object",
        "description": "A custom role. System roles appear here too and cannot be modified or deleted.",
        "properties": {
          "id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "description": "Up to 50 characters, unique within the organisation."
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "base_role": {
            "type": "string",
            "nullable": true,
            "description": "The built-in role this one started from."
          },
          "priority": {
            "type": "integer",
            "description": "Decides which role wins when a member holds several."
          },
          "is_system": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "1 for a built-in role."
          },
          "member_count": {
            "type": "integer",
            "description": "How many members hold it."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Entitlements": {
        "type": "object",
        "description": "What this organisation's plan and platform-owner toggles allow.",
        "properties": {
          "tier": {
            "type": "string",
            "example": "pro"
          },
          "isLegacy": {
            "type": "boolean",
            "description": "Legacy organisations predate metering and bypass plan gates."
          },
          "features": {
            "type": "object",
            "additionalProperties": {
              "type": "boolean"
            }
          },
          "limits": {
            "type": "object",
            "additionalProperties": {
              "type": "integer",
              "nullable": true
            }
          }
        }
      },
      "UsageSummary": {
        "type": "object",
        "description": "Usage for the current UTC calendar month.",
        "properties": {
          "submissions_used": {
            "type": "integer"
          },
          "submissions_limit": {
            "type": "integer",
            "nullable": true,
            "description": "Null means unlimited."
          },
          "submissions_remaining": {
            "type": "integer",
            "nullable": true,
            "description": "Null when the limit is unlimited — not zero."
          },
          "signatures_used": {
            "type": "integer"
          },
          "signatures_limit": {
            "type": "integer",
            "nullable": true,
            "description": "Null means unlimited."
          },
          "signatures_remaining": {
            "type": "integer",
            "nullable": true,
            "description": "Null when the limit is unlimited — not zero."
          },
          "period_start": {
            "type": "string",
            "format": "date-time",
            "description": "Midnight UTC on the first of the month."
          }
        }
      },
      "Notification": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "user_id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "description": "Category, used to pick an icon — e.g. `submission`, `billing`."
          },
          "is_read": {
            "type": "boolean"
          },
          "link": {
            "type": "string",
            "nullable": true,
            "description": "Relative in-app path the notification points at."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Connection": {
        "type": "object",
        "description": "A connection to an external system. Credentials are stored encrypted and never returned.",
        "properties": {
          "id": {
            "type": "string"
          },
          "provider": {
            "type": "string",
            "description": "Which system — e.g. `priority`."
          },
          "display_name": {
            "type": "string",
            "nullable": true
          },
          "instance_url": {
            "type": "string",
            "nullable": true,
            "description": "The customer's own installation, for self-hosted providers."
          },
          "status": {
            "type": "string",
            "description": "Whether the connection is currently working."
          },
          "last_probe_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When SignFlow last checked it."
          },
          "last_error": {
            "type": "string",
            "nullable": true,
            "description": "Why the last check failed, scrubbed of anything sensitive."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "provider_available": {
            "type": "boolean",
            "description": "Whether its app is still available. `false` only for a flow app's connection whose app SignFlow no longer offers: it can only be removed, and every other call on it answers `422` with `code: provider_unavailable`."
          },
          "used_by_flows": {
            "type": "integer",
            "description": "How many of your flows (drafts or the versions they run, the trash left out) use this connection. Present only when flows are enabled for your organization."
          }
        }
      },
      "ConnectionUsage": {
        "type": "object",
        "required": [
          "flow_id",
          "flow_name",
          "module_id",
          "module_type",
          "module_label",
          "active"
        ],
        "properties": {
          "flow_id": {
            "type": "string"
          },
          "flow_name": {
            "type": "string"
          },
          "module_id": {
            "type": "integer"
          },
          "module_type": {
            "type": "string",
            "description": "The module's type, e.g. `http:MakeRequest`; the flows apps catalog names it in your language."
          },
          "module_label": {
            "type": [
              "string",
              "null"
            ],
            "description": "The label the module was given; null when none was."
          },
          "active": {
            "type": "boolean",
            "description": "The flow is on and this module is in the version it runs."
          }
        }
      },
      "ConnectRule": {
        "type": "object",
        "description": "A rule: when something happens on the connected system, send a SignFlow document. `inbound_token` is never included here — read it from the dedicated inbound-url endpoint.",
        "properties": {
          "id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "connection_id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "object_id": {
            "type": "string",
            "nullable": true,
            "description": "The object on the far side this rule watches."
          },
          "template_id": {
            "type": "string",
            "nullable": true,
            "description": "The template to send. Required before the rule can be activated."
          },
          "trigger_config": {
            "type": "string",
            "nullable": true,
            "description": "JSON. What makes the rule fire."
          },
          "trigger_kind": {
            "type": "string",
            "nullable": true,
            "description": "How it fires — the provider pushes to SignFlow, or SignFlow polls."
          },
          "signer_map": {
            "type": "string",
            "nullable": true,
            "description": "JSON. Where each signer's name and address come from on the record."
          },
          "field_map": {
            "type": "string",
            "nullable": true,
            "description": "JSON. Which record fields fill which template fields."
          },
          "writeback_config": {
            "type": "string",
            "nullable": true,
            "description": "JSON. What to write back once the document is signed."
          },
          "flow_config": {
            "type": "string",
            "nullable": true,
            "description": "JSON. The steps the rule runs."
          },
          "is_active": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "subscription_handle": {
            "type": "string",
            "nullable": true,
            "description": "The provider's own handle for the subscription, where it has one."
          },
          "poll_watermark": {
            "type": "string",
            "nullable": true,
            "description": "How far a polling rule has read."
          },
          "last_run_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ConnectRuleRun": {
        "type": "object",
        "description": "One firing of a rule.",
        "properties": {
          "id": {
            "type": "string"
          },
          "rule_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "record_id": {
            "type": "string",
            "nullable": true,
            "description": "The record on the far side that triggered it."
          },
          "submission_id": {
            "type": "string",
            "nullable": true,
            "description": "The submission it created, where it got that far."
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "finished_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "Agency": {
        "type": "object",
        "description": "A white-label reseller: its own brand, hostnames, price book, billing account and clients.",
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "organization_id": {
            "type": "string",
            "description": "The agency's own workspace organisation. Note this is not one of its clients."
          },
          "company_name": {
            "type": "string"
          },
          "product_name": {
            "type": "string"
          },
          "legal_entity": {
            "type": "string"
          },
          "support_email": {
            "type": "string",
            "nullable": true
          },
          "support_phone": {
            "type": "string",
            "nullable": true
          },
          "default_language": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "suspended",
              "offboarding"
            ]
          },
          "fallback_hostname": {
            "type": "string",
            "nullable": true,
            "description": "A subdomain on the neutral domain, so the agency is reachable before its own is set up."
          },
          "legal_reviewed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Until this is set, the agency cannot create clients."
          },
          "revenue_share_bps": {
            "type": "integer",
            "description": "The agency's share, in basis points (10000 = 100%). Platform owners only."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": true
      },
      "AgencyBrand": {
        "type": "object",
        "description": "How the product presents itself to an agency's customers. Image fields must be a SignFlow storage path or an https URL; colours must be #RRGGBB.",
        "properties": {
          "productName": {
            "type": "string",
            "description": "What the agency calls the product."
          },
          "companyName": {
            "type": "string"
          },
          "legalEntity": {
            "type": "string",
            "description": "The name printed on documents and invoices."
          },
          "supportEmail": {
            "type": "string",
            "format": "email"
          },
          "supportPhone": {
            "type": "string"
          },
          "defaultLanguage": {
            "type": "string",
            "enum": [
              "en",
              "he"
            ]
          },
          "primaryColor": {
            "type": "string",
            "pattern": "^#[0-9A-Fa-f]{6}$"
          },
          "secondaryColor": {
            "type": "string",
            "pattern": "^#[0-9A-Fa-f]{6}$"
          },
          "logoUrl": {
            "type": "string"
          },
          "faviconUrl": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "PriceBook": {
        "type": "object",
        "description": "What an agency charges its own clients. Prices are in agorot and exclude VAT. Anything left unset falls through to SignFlow's list price.",
        "properties": {
          "plans": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "monthlyPriceAgorot": {
                  "type": "integer"
                },
                "yearlyPriceAgorot": {
                  "type": "integer"
                }
              }
            },
            "description": "Keyed by tier."
          },
          "addons": {
            "type": "object",
            "additionalProperties": true,
            "description": "Keyed by add-on."
          },
          "packs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "credits": {
                  "type": "integer"
                },
                "priceAgorot": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "additionalProperties": true
      },
      "Plan": {
        "type": "object",
        "description": "A subscription tier. Prices are in agorot (1/100 shekel) and exclude VAT.",
        "properties": {
          "tier": {
            "type": "string"
          },
          "monthlyPriceAgorot": {
            "type": "integer"
          },
          "yearlyPriceAgorot": {
            "type": "integer"
          }
        },
        "additionalProperties": true
      },
      "BillingState": {
        "type": "object",
        "description": "The whole billing picture for one organisation.",
        "properties": {
          "subscription": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Subscription"
              }
            ],
            "nullable": true
          },
          "entitlements": {
            "type": "object",
            "additionalProperties": true,
            "description": "What the plan allows. `creditsPerMonth` is null for a legacy organisation, meaning unlimited — not zero. `isLegacy` marks organisations that predate the current plans and are exempt from its gates.",
            "properties": {
              "isLegacy": {
                "type": "boolean"
              },
              "creditsPerMonth": {
                "type": "integer",
                "nullable": true,
                "description": "Null means unlimited."
              }
            }
          },
          "credits": {
            "type": "object",
            "additionalProperties": true,
            "description": "The credit balance and its components."
          },
          "paymentMethod": {
            "type": "object",
            "nullable": true,
            "description": "The saved card, or null. Only the last four digits and expiry are kept.",
            "properties": {
              "last4": {
                "type": "string"
              },
              "expMonth": {
                "type": "integer",
                "nullable": true
              },
              "expYear": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "addons": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "addonKey": {
                  "type": "string"
                },
                "status": {
                  "type": "string"
                },
                "priceAgorot": {
                  "type": "integer"
                }
              }
            }
          },
          "upcomingCharge": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "What the next renewal will cost."
          },
          "creditUsage": {
            "type": "object",
            "additionalProperties": true,
            "description": "How much of the monthly allowance has gone."
          }
        }
      },
      "Invoice": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "number": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string"
          },
          "amountAgorot": {
            "type": "integer",
            "nullable": true
          },
          "issuedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "dueAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Bank-transfer invoices only."
          },
          "documentUrl": {
            "type": "string",
            "nullable": true,
            "description": "Where to download the invoice."
          }
        },
        "additionalProperties": true
      },
      "Payment": {
        "type": "object",
        "description": "A signer payment attempt recorded against a submission, normalised across providers.",
        "properties": {
          "id": {
            "type": "string"
          },
          "submission_id": {
            "type": "string"
          },
          "signer_id": {
            "type": "string",
            "nullable": true
          },
          "provider": {
            "type": "string",
            "enum": [
              "pelecard",
              "payplus",
              "sumit"
            ]
          },
          "action_type": {
            "type": "string",
            "enum": [
              "J4",
              "J5",
              "J2"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "tokenized",
              "paid",
              "failed"
            ]
          },
          "amount_agorot": {
            "type": "integer",
            "nullable": true
          },
          "currency": {
            "type": "string"
          },
          "card_saved": {
            "type": "boolean"
          },
          "card_last_four": {
            "type": "string",
            "nullable": true
          },
          "reference": {
            "type": "string",
            "nullable": true
          },
          "error_code": {
            "type": "string",
            "nullable": true
          },
          "error_message": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "UploadedFile": {
        "type": "object",
        "description": "The echo returned straight after an attachment upload.",
        "properties": {
          "id": {
            "type": "string"
          },
          "file_id": {
            "type": "string",
            "description": "Same value as `id`; both are present so either name works."
          },
          "name": {
            "type": "string",
            "description": "The original filename."
          },
          "type": {
            "type": "string",
            "description": "MIME type."
          },
          "size": {
            "type": "integer",
            "description": "Bytes."
          }
        }
      },
      "CreatedApiToken": {
        "type": "object",
        "description": "Returned only by `POST /api/v1/tokens`. `token` appears in this response and nowhere else — store it now.",
        "properties": {
          "id": {
            "type": "string"
          },
          "token": {
            "type": "string",
            "description": "The secret, `sf_` prefixed. Send it raw in the `Authorization` header — no `Bearer` prefix."
          },
          "name": {
            "type": "string"
          },
          "token_prefix": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            }
          },
          "is_active": {
            "type": "boolean"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateApiTokenInput": {
        "type": "object",
        "required": [
          "name",
          "organization_id"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "How the token appears in the list. Name it after the system that will hold it."
          },
          "organization_id": {
            "type": "string",
            "description": "Must be an organisation you belong to. An API token can only mint tokens inside its own organisation."
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            },
            "description": "Defaults to `[\"templates:read\", \"submissions:write\"]` — enough to send documents and nothing else."
          },
          "expiresInDays": {
            "type": "integer",
            "description": "Omit for a token that never expires."
          }
        }
      },
      "UpdateApiTokenInput": {
        "type": "object",
        "description": "Partial. Send `name`, `scopes`, or both; sending neither is a 400.",
        "properties": {
          "name": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            }
          }
        }
      },
      "UpdateWebhookInput": {
        "type": "object",
        "description": "Partial: omitted keys keep their current value. Deactivating an endpoint, or pointing it at a new URL, cancels deliveries still queued against the old one.",
        "properties": {
          "name": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "is_active": {
            "type": "boolean"
          },
          "headers": {
            "type": "object",
            "description": "A JSON merge patch, because reads are masked and you cannot round-trip the full map: a string sets or replaces that header, `null` removes it, and headers you omit are kept. Names match case-insensitively (patching `x-leonai-origin` replaces a stored `X-LeonAI-Origin`, and the new spelling is what gets sent). The merged result must still satisfy every rule in `WebhookHeaders` — including the 10-header bound and the reserved list.",
            "additionalProperties": {
              "type": "string",
              "nullable": true,
              "minLength": 1,
              "maxLength": 1024
            },
            "example": {
              "X-LeonAI-Origin": "new-value",
              "X-Old-Header": null
            }
          }
        }
      },
      "RetryDeliveriesInput": {
        "type": "object",
        "required": [
          "organization_id"
        ],
        "description": "Narrow the retry with `delivery_id` (one row) or `webhook_url` (one endpoint). With neither, every failed delivery in the organisation is retried — which can fire a lot of outbound requests at once, hence the rate limit.",
        "properties": {
          "organization_id": {
            "type": "string",
            "description": "May also be given as a query parameter."
          },
          "delivery_id": {
            "type": "string"
          },
          "webhook_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "SubmissionFile": {
        "type": "object",
        "description": "A file attached to a submission through a file field.",
        "properties": {
          "id": {
            "type": "string"
          },
          "field_key": {
            "type": "string",
            "description": "The field on the document this file was uploaded into."
          },
          "original_filename": {
            "type": "string"
          },
          "file_type": {
            "type": "string",
            "description": "MIME type."
          },
          "file_size": {
            "type": "integer",
            "description": "Bytes."
          },
          "uploaded_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateOrganizationInput": {
        "type": "object",
        "required": [
          "name",
          "slug"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Up to 100 characters."
          },
          "slug": {
            "type": "string",
            "description": "Lowercase letters, digits and hyphens."
          },
          "logo_url": {
            "type": "string",
            "description": "A SignFlow storage path or an https URL. Nothing else is accepted."
          },
          "settings": {
            "$ref": "#/components/schemas/OrganizationSettings"
          },
          "agencyId": {
            "type": "string",
            "description": "Place the organisation under a white-label agency."
          },
          "turnstileToken": {
            "type": "string",
            "maxLength": 2048,
            "description": "Cloudflare Turnstile token (action `signup`). Required for browser sessions once bot protection is enabled."
          }
        }
      },
      "UpdateOrganizationInput": {
        "type": "object",
        "description": "Partial: only the keys present are written.",
        "properties": {
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "logo_url": {
            "type": "string"
          },
          "square_icon_url": {
            "type": "string"
          },
          "settings": {
            "$ref": "#/components/schemas/OrganizationSettings"
          }
        }
      },
      "OrganizationSettings": {
        "type": "object",
        "description": "How this organisation's signing pages and emails look and behave. Everything is optional; unset keys fall back to platform defaults.",
        "properties": {
          "timezone": {
            "type": "string",
            "description": "IANA name, from a supported list — e.g. `Asia/Jerusalem`, `Europe/London`."
          },
          "dateFormat": {
            "type": "string",
            "enum": [
              "MM/DD/YYYY",
              "DD/MM/YYYY",
              "YYYY-MM-DD"
            ]
          },
          "defaultLanguage": {
            "type": "string",
            "enum": [
              "en",
              "he"
            ]
          },
          "brandColor": {
            "type": "string",
            "pattern": "^#[0-9A-Fa-f]{6}$"
          },
          "brandSecondaryColor": {
            "type": "string",
            "pattern": "^#[0-9A-Fa-f]{6}$"
          },
          "signingBackgroundColor": {
            "type": "string",
            "pattern": "^#[0-9A-Fa-f]{6}$"
          },
          "senderName": {
            "type": "string",
            "description": "The From name on invitation emails. Up to 100 characters."
          },
          "linkPreviewTitle": {
            "type": "string",
            "description": "Open Graph title, shown when a signing link is pasted into WhatsApp or Slack. Up to 100 characters."
          },
          "linkPreviewDescription": {
            "type": "string",
            "description": "Open Graph description. Up to 200 characters."
          },
          "defaultExpirationDays": {
            "type": "integer",
            "description": "1–365."
          },
          "autoReminderDays": {
            "type": "integer",
            "nullable": true,
            "description": "Send a reminder this many days after inviting. 1–30, or null to disable."
          },
          "allowDecline": {
            "type": "boolean",
            "description": "Whether signers may refuse to sign."
          },
          "notificationEmail": {
            "type": "string",
            "format": "email"
          },
          "replyToEmail": {
            "type": "string",
            "format": "email"
          },
          "customDomain": {
            "type": "string",
            "description": "Read-only here; set it through the custom-domain endpoints."
          },
          "customDomainVerified": {
            "type": "boolean"
          },
          "customDomainVerifiedAt": {
            "type": "string",
            "format": "date-time"
          },
          "emailDomain": {
            "type": "string",
            "description": "Read-only here; set it through the email-domain endpoints."
          },
          "emailDomainStatus": {
            "type": "string",
            "enum": [
              "not_started",
              "pending",
              "verified",
              "failed"
            ]
          },
          "emailDomainVerifiedAt": {
            "type": "string",
            "format": "date-time"
          },
          "emailDomainRecords": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DnsRecord"
            }
          }
        }
      },
      "DnsRecord": {
        "type": "object",
        "description": "A record to publish at your DNS provider.",
        "properties": {
          "record": {
            "type": "string",
            "description": "Which purpose it serves — e.g. `DKIM`, `SPF`."
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "description": "e.g. `TXT`, `CNAME`, `MX`."
          },
          "value": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "Whether SignFlow has seen it resolve yet."
          },
          "ttl": {
            "type": "string"
          },
          "priority": {
            "type": "integer",
            "description": "MX records only."
          }
        }
      },
      "PaymentProviderConfig": {
        "type": "object",
        "description": "The signer-payment setup, with no secret ever returned.",
        "properties": {
          "configured": {
            "type": "boolean"
          },
          "unusable": {
            "type": "boolean",
            "description": "True when a configuration exists but cannot be used — undecryptable after a key rotation, or missing a field the provider now requires. Distinct from never having been set up."
          },
          "provider": {
            "type": "string",
            "description": "e.g. `pelecard`, `payplus`, `sumit`."
          },
          "credential_masked": {
            "type": "string",
            "nullable": true,
            "description": "One non-secret field, masked, so an operator can confirm which terminal is configured."
          },
          "credential_masked_key": {
            "type": "string",
            "nullable": true,
            "description": "Which field `credential_masked` shows."
          },
          "terminal_masked": {
            "type": "string",
            "nullable": true,
            "description": "Legacy alias of `credential_masked`, kept for the deployed settings UI."
          },
          "default_action_type": {
            "type": "string",
            "nullable": true,
            "description": "The provider's own code for what a payment does by default."
          },
          "default_intent": {
            "type": "string",
            "nullable": true,
            "description": "The same choice in neutral terms — charge, hold, or save a card."
          },
          "auto_capture_on_complete": {
            "type": "boolean",
            "description": "Whether a hold is captured when the document completes. Off unless explicitly enabled: a hold stays a hold until somebody decides to take the money."
          },
          "providers": {
            "type": "array",
            "description": "The catalogue of supported providers and the credential fields each needs — field names and shapes only, never any stored value. Enough to render the setup form without hard-coding it.",
            "items": {
              "$ref": "#/components/schemas/PaymentProviderDescriptor"
            }
          }
        }
      },
      "PaymentProviderDescriptor": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "display_name": {
            "type": "string"
          },
          "intents": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Which of charge / hold / save-card this provider supports."
          },
          "native_action_type": {
            "type": "object",
            "additionalProperties": true,
            "description": "Maps each neutral intent to the provider's own code."
          },
          "credential_fields": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "label_key": {
                  "type": "string",
                  "description": "i18n key for the label."
                },
                "type": {
                  "type": "string",
                  "description": "`password` marks a field that must never be displayed back."
                },
                "required": {
                  "type": "boolean"
                },
                "max_length": {
                  "type": "integer"
                },
                "help": {
                  "type": "string",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "SetPaymentProviderInput": {
        "type": "object",
        "required": [
          "provider"
        ],
        "properties": {
          "provider": {
            "type": "string",
            "description": "One of the ids from the catalogue."
          },
          "credentials": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Keyed by the provider's `credential_fields`. Required when setting up, and again whenever the provider changes."
          },
          "default_action_type": {
            "type": "string",
            "description": "The provider's own action code. `default_intent` in the read response is the neutral form of the same choice."
          },
          "auto_capture_on_complete": {
            "type": "boolean",
            "description": "Capture a hold when the document completes. Defaults to false."
          }
        }
      },
      "OrganizationFeatures": {
        "type": "object",
        "description": "Feature access, in two views. The top-level flags are EFFECTIVE access, from any source — plan, purchased add-on, or platform-owner grant. `manual` is only the owner grants. They can disagree: a bought add-on shows as effective while its manual flag stays 0, and revoking the manual grant would not take it away.",
        "properties": {
          "organization_id": {
            "type": "string"
          },
          "feature_sso": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "SAML / OIDC single sign-on."
          },
          "feature_branding": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Custom logo and colours on the signing ceremony."
          },
          "feature_custom_domain": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Serve signing links from your own domain."
          },
          "feature_custom_email": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Send invitations from your own domain."
          },
          "feature_team_permissions": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Custom roles and per-member permission grants."
          },
          "feature_ai_smart_detect": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "AI-assisted field detection in the builder."
          },
          "feature_signer_payments": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Collect a payment or a card from the signer before they submit."
          },
          "feature_integrations": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "SignFlow Connect — rules that push signed data to other systems."
          },
          "feature_mcp": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "SignFlow MCP connector — AI assistants use SignFlow through the Model Context Protocol. Effective with the platform-owner grant, with a plan that includes API access (the plans that may create API tokens), or for a legacy organisation."
          },
          "feature_flow_engine": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "readOnly": true,
            "description": "The flow builder (invitation only). Needs `feature_integrations` as well. Read-only: only the platform owner turns it on, there is no `manual` copy, and PUT ignores it. 0 when it cannot be read."
          },
          "manual": {
            "type": "object",
            "description": "The platform-owner grants alone.",
            "properties": {
              "feature_sso": {
                "type": "integer",
                "enum": [
                  0,
                  1
                ],
                "description": "SAML / OIDC single sign-on."
              },
              "feature_branding": {
                "type": "integer",
                "enum": [
                  0,
                  1
                ],
                "description": "Custom logo and colours on the signing ceremony."
              },
              "feature_custom_domain": {
                "type": "integer",
                "enum": [
                  0,
                  1
                ],
                "description": "Serve signing links from your own domain."
              },
              "feature_custom_email": {
                "type": "integer",
                "enum": [
                  0,
                  1
                ],
                "description": "Send invitations from your own domain."
              },
              "feature_team_permissions": {
                "type": "integer",
                "enum": [
                  0,
                  1
                ],
                "description": "Custom roles and per-member permission grants."
              },
              "feature_ai_smart_detect": {
                "type": "integer",
                "enum": [
                  0,
                  1
                ],
                "description": "AI-assisted field detection in the builder."
              },
              "feature_signer_payments": {
                "type": "integer",
                "enum": [
                  0,
                  1
                ],
                "description": "Collect a payment or a card from the signer before they submit."
              },
              "feature_integrations": {
                "type": "integer",
                "enum": [
                  0,
                  1
                ],
                "description": "SignFlow Connect — rules that push signed data to other systems."
              },
              "feature_mcp": {
                "type": "integer",
                "enum": [
                  0,
                  1
                ],
                "description": "SignFlow MCP connector — AI assistants use SignFlow through the Model Context Protocol."
              }
            }
          }
        }
      },
      "SetOrganizationFeaturesInput": {
        "type": "object",
        "description": "Writes the manual grants, every flag at once: a flag the body omits (or sends as 0) is set off. To change one flag, send all of them, with the others as GET's `manual` gives them. `feature_flow_engine` is read-only: PUT ignores it and leaves it as it is.",
        "properties": {
          "feature_sso": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "SAML / OIDC single sign-on."
          },
          "feature_branding": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Custom logo and colours on the signing ceremony."
          },
          "feature_custom_domain": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Serve signing links from your own domain."
          },
          "feature_custom_email": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Send invitations from your own domain."
          },
          "feature_team_permissions": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Custom roles and per-member permission grants."
          },
          "feature_ai_smart_detect": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "AI-assisted field detection in the builder."
          },
          "feature_signer_payments": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Collect a payment or a card from the signer before they submit."
          },
          "feature_integrations": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "SignFlow Connect — rules that push signed data to other systems."
          },
          "feature_mcp": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "SignFlow MCP connector — AI assistants use SignFlow through the Model Context Protocol. The owner grant alone: an organisation whose plan includes API access, or a legacy organisation, has it whatever this says."
          }
        }
      },
      "Profile": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "full_name": {
            "type": "string",
            "nullable": true
          },
          "avatar_url": {
            "type": "string",
            "nullable": true
          },
          "company_name": {
            "type": "string",
            "nullable": true
          },
          "timezone": {
            "type": "string",
            "nullable": true
          },
          "language": {
            "type": "string",
            "nullable": true,
            "description": "`en` or `he`."
          },
          "theme": {
            "type": "string",
            "nullable": true,
            "description": "`light`, `dark` or `system`."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "NotificationPreferences": {
        "type": "object",
        "description": "Which document events email you. Independent of webhooks, which are configured per organisation.",
        "properties": {
          "emailOnSigned": {
            "type": "boolean",
            "description": "A signer has signed."
          },
          "emailOnCompleted": {
            "type": "boolean",
            "description": "Every signer is done and the document is final."
          },
          "emailOnDeclined": {
            "type": "boolean",
            "description": "A signer refused to sign."
          }
        }
      },
      "CodedError": {
        "type": "object",
        "description": "The error shape used by the roles and permissions endpoints: a message, a stable machine code, and sometimes the offending values.",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable."
          },
          "code": {
            "type": "string",
            "description": "Stable identifier — e.g. `VALIDATION_ERROR`, `IN_USE`, `MEMBER_NOT_FOUND`. Branch on this, not on the message."
          },
          "details": {
            "type": "object",
            "additionalProperties": true,
            "description": "Present where it helps: `invalidIds` for rejected ids, `memberCount` for a role still in use."
          }
        }
      },
      "RoleInput": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Up to 50 characters."
          },
          "description": {
            "type": "string"
          },
          "permissions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Permission ids from `GET /api/v1/organizations/{id}/permissions`. An unknown id fails the whole call."
          },
          "base_role": {
            "type": "string",
            "description": "Start from a built-in role's permissions."
          },
          "priority": {
            "type": "integer"
          }
        }
      },
      "PermissionOverride": {
        "type": "object",
        "description": "A per-member grant or denial sitting on top of their roles.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Pass this as `permId` to remove the override."
          },
          "permission": {
            "type": "string"
          },
          "granted": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "1 for a grant, 0 for a denial."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Null when it does not expire."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ResourceAccess": {
        "type": "object",
        "description": "Access to one specific template or submission.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Pass this as `accessId` to revoke."
          },
          "resource_type": {
            "type": "string",
            "enum": [
              "template",
              "submission"
            ]
          },
          "resource_id": {
            "type": "string"
          },
          "access_level": {
            "type": "string",
            "enum": [
              "view",
              "edit",
              "manage",
              "none"
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ConnectRuleInput": {
        "type": "object",
        "description": "Create and update take the same shape. On create, `connection_id` is required. Everything else can be filled in over several calls — an inactive rule is not validated, so it can be built up incrementally.",
        "properties": {
          "connection_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "object_id": {
            "type": "string"
          },
          "template_id": {
            "type": "string"
          },
          "trigger_config": {
            "type": "object",
            "additionalProperties": true
          },
          "signer_map": {
            "type": "object",
            "additionalProperties": true
          },
          "field_map": {
            "type": "object",
            "additionalProperties": true
          },
          "writeback_config": {
            "type": "object",
            "additionalProperties": true
          },
          "flow_config": {
            "type": "object",
            "additionalProperties": true,
            "description": "Must be a JSON object, not a string or array."
          }
        }
      },
      "SigningGoneError": {
        "type": "object",
        "description": "Returned with 410 when the document can no longer be signed. Show it to the signer — it is an outcome, not a fault.",
        "properties": {
          "error": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "Why: `expired`, `signed`, `declined`, or the submission's own status."
          }
        }
      },
      "SigningSession": {
        "type": "object",
        "description": "Everything the ceremony needs to render: who is signing, what they are signing, and which fields they fill.",
        "properties": {
          "signer": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string",
                "nullable": true
              },
              "email": {
                "type": "string",
                "nullable": true
              },
              "role": {
                "type": "string",
                "nullable": true
              },
              "status": {
                "$ref": "#/components/schemas/SignerStatus"
              },
              "signing_token": {
                "type": "string",
                "description": "This signer's token. Treat as a secret — it is the whole credential."
              }
            }
          },
          "submission": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string",
                "nullable": true,
                "description": "The document's name."
              },
              "pdf_url": {
                "type": "string",
                "nullable": true
              },
              "logo_url": {
                "type": "string",
                "nullable": true,
                "description": "The organisation's logo, for the ceremony header."
              },
              "language": {
                "type": "string",
                "description": "`en` or `he`. Drives the interface and the text direction."
              },
              "allow_download": {
                "type": "boolean"
              },
              "allow_print": {
                "type": "boolean"
              },
              "allow_decline": {
                "type": "boolean",
                "description": "Whether this signer may refuse."
              },
              "success_message": {
                "type": "string",
                "nullable": true,
                "description": "Shown after signing."
              },
              "redirect_url": {
                "type": "string",
                "nullable": true,
                "description": "Where to send the signer afterwards."
              },
              "fields_data": {
                "type": "object",
                "additionalProperties": true,
                "description": "Prefilled values, filtered to what this signer is allowed to see."
              }
            }
          },
          "fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Field"
            },
            "description": "Every field on the document."
          },
          "document_fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Field"
            },
            "description": "The fields printed onto the PDF."
          },
          "data_collection_fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Field"
            },
            "description": "Fields collected from the signer but not printed on the document."
          }
        }
      },
      "Subscription": {
        "type": "object",
        "properties": {
          "tier": {
            "type": "string"
          },
          "interval": {
            "type": "string",
            "description": "`monthly`, `quarterly` or `yearly`."
          },
          "status": {
            "type": "string"
          },
          "priceAgorot": {
            "type": "integer",
            "description": "Excluding VAT."
          },
          "comped": {
            "type": "boolean",
            "description": "Granted free by a platform admin."
          },
          "currentPeriodStart": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "currentPeriodEnd": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "graceExpiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "How long a failed payment is tolerated before access is cut."
          },
          "cancelAtPeriodEnd": {
            "type": "boolean"
          },
          "pendingTier": {
            "type": "string",
            "nullable": true,
            "description": "A downgrade already scheduled for the period end."
          },
          "billingMethod": {
            "type": "string",
            "description": "`credit_card` or `bank_transfer`."
          },
          "paymentTermsDays": {
            "type": "integer",
            "description": "Net terms, for bank transfer."
          },
          "autoReload": {
            "type": "object",
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "packCredits": {
                "type": "integer",
                "nullable": true
              },
              "threshold": {
                "type": "integer",
                "nullable": true
              }
            }
          }
        }
      },
      "AgencyUpdateInput": {
        "type": "object",
        "description": "Partial. Branding and support fields are open to agency staff; the wholesale terms — revenue share and anything else governing what the agency pays SignFlow — are platform owners only and are refused for anyone else.",
        "properties": {
          "companyName": {
            "type": "string"
          },
          "productName": {
            "type": "string"
          },
          "legalEntity": {
            "type": "string"
          },
          "supportEmail": {
            "type": "string",
            "format": "email"
          },
          "supportPhone": {
            "type": "string"
          },
          "defaultLanguage": {
            "type": "string",
            "enum": [
              "en",
              "he"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "suspended",
              "offboarding"
            ]
          },
          "primaryColor": {
            "type": "string",
            "pattern": "^#[0-9A-Fa-f]{6}$"
          },
          "secondaryColor": {
            "type": "string",
            "pattern": "^#[0-9A-Fa-f]{6}$"
          },
          "logoUrl": {
            "type": "string"
          },
          "faviconUrl": {
            "type": "string"
          },
          "revenueShareBps": {
            "type": "integer",
            "description": "Platform owners only. 0–10000."
          }
        },
        "additionalProperties": true
      },
      "FlowSchedule": {
        "type": "object",
        "description": "When a flow runs, in Make's shape (the Make SDK's `Scheduling`). Times are wall-clock times in the flow's time zone. A flow with no schedule of its own runs every 15 minutes, as a new Make scenario does.",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "immediately",
              "indefinitely",
              "once",
              "daily",
              "weekly",
              "monthly",
              "yearly",
              "on-demand"
            ],
            "description": "`indefinitely` = at regular intervals. Weekdays is `weekly` with days 1–5; Specified dates is `yearly`. `immediately` is for a flow that starts with an instant trigger (Priority's Watch records (instant), Custom webhook): each item its webhook receives starts one run. A flow that starts with Watch records (instant) runs on `immediately` only; one that starts with Custom webhook may run on any schedule it is given (not the default every 15 minutes of a flow with none), each run taking up to the module's `maxResults` of its queued requests."
          },
          "interval": {
            "type": "integer",
            "minimum": 60,
            "default": 900,
            "description": "`indefinitely`: seconds between runs. The first run is one interval after the flow is turned on or the schedule is set; a flow that starts with Custom webhook first runs at once when it is turned on. With `restrict`, a flow turned on while no period is open first runs exactly when one opens."
          },
          "date": {
            "type": "string",
            "format": "date-time",
            "description": "`once`: when."
          },
          "days": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "`weekly`: 0 (Sunday) to 6. `monthly` and `yearly`: 1 to 31; a month without that day is skipped."
          },
          "months": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 1,
              "maximum": 12
            },
            "description": "`yearly`: the months."
          },
          "time": {
            "description": "`daily`, `weekly`, `monthly`, `yearly`: `HH:mm`, or a list of them. Returned as a sorted list.",
            "oneOf": [
              {
                "type": "string",
                "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
              },
              {
                "type": "array",
                "items": {
                  "type": "string",
                  "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                }
              }
            ]
          },
          "between": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "items": {
              "type": [
                "string",
                "null"
              ],
              "format": "date-time"
            },
            "description": "Optional start and end; either may be null. Nothing runs before the start or after the end."
          },
          "maximum_runs_per_minute": {
            "type": "integer",
            "minimum": 1,
            "description": "`immediately`: at most this many runs start in any 60 seconds, the oldest items first; the rest wait in the flow's queue. Left out: no limit."
          },
          "restrict": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/FlowScheduleRestrict"
            },
            "description": "Optional: the periods a run may start in (Make's advanced scheduling), read on the flow's clock in its time zone, daylight saving included. A run the schedule starts begins only at a moment one of the periods allows; on Immediately and On demand the periods are kept but time nothing. At regular intervals, the run lands exactly when a period opens (09:00), then repeats every interval while it stays open; a daily, weekly, monthly, yearly or once time outside every period is skipped. Reruns after a connection, timeout or rate-limit error ignore it. A schedule whose times its periods never allow has no runs left (`schedule_ended`). An empty list restricts nothing and is not stored."
          }
        },
        "additionalProperties": false
      },
      "FlowScheduleRestrict": {
        "type": "object",
        "description": "One period a scheduled run may start in. Inside a period, every key it sets must match; a key left out matches anything, so `{}` allows any time.",
        "properties": {
          "days": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "integer",
              "minimum": 0,
              "maximum": 6
            },
            "description": "Days of the week: 0 (Sunday) to 6 (Saturday). Returned sorted, each once. Make also saves 7 to 31, which match no day; they are refused here."
          },
          "months": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "integer",
              "minimum": 1,
              "maximum": 12
            },
            "description": "Months: 1 (January) to 12. Returned sorted, each once."
          },
          "time": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "items": {
              "type": [
                "string",
                "null"
              ],
              "pattern": "^([01]?[0-9]|2[0-3]):[0-5]?[0-9](:00?)?(\\.0+)?$"
            },
            "description": "[from, until] on the flow's clock: from ≤ time < until; null is an open end. Make's `H:mm[:ss]` is accepted with zero seconds. An until before the from crosses midnight (22:00 to 06:00), and the part after midnight counts on that day's own date. From and until may not be equal. Returned as `HH:mm`."
          }
        },
        "additionalProperties": false
      },
      "FlowLifecycle": {
        "type": "object",
        "description": "Whether a flow is on, its schedule, and when it runs next.",
        "properties": {
          "id": {
            "type": "string"
          },
          "is_active": {
            "type": "boolean"
          },
          "published_version_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The saved version an active flow runs."
          },
          "schedule": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/FlowSchedule"
              },
              {
                "type": "null"
              }
            ],
            "description": "The schedule the flow runs on; null only when a stored schedule no longer reads."
          },
          "timezone": {
            "type": [
              "string",
              "null"
            ],
            "description": "An IANA time zone; null means the organization's (UTC when it has none)."
          },
          "next_run_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the flow starts next; null while off, On demand, or with no runs left."
          },
          "consecutive_errors": {
            "type": "integer",
            "description": "Scheduled runs in a row that ended with Error."
          },
          "deactivated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Set when errors turned the flow off; cleared when it is turned on again."
          },
          "deactivated_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "The last error's message, when errors turned the flow off."
          }
        }
      },
      "FlowStartChoice": {
        "type": "object",
        "description": "Where a trigger that polls starts (Make's Choose where to start): what its first check reads. `from_now`: the records it finds then are only noted, and later checks start what changes after; `since_date`: records since `date`; `all`: every record; `manual`: the records after the one named by `record_id` (as the trigger's records name their record, `recordId`); `from_id`: records whose key, one number, is at least `id`. What a trigger offers depends on its settings (`options`).",
        "required": [
          "option"
        ],
        "properties": {
          "option": {
            "type": "string",
            "enum": [
              "from_now",
              "since_date",
              "all",
              "manual",
              "from_id"
            ]
          },
          "date": {
            "type": "string",
            "format": "date-time",
            "description": "`since_date`: an ISO 8601 date and time with its offset; kept in UTC."
          },
          "id": {
            "type": "integer",
            "description": "`from_id`: the lowest key that starts."
          },
          "record_id": {
            "type": "string",
            "maxLength": 1000,
            "description": "`manual`: the record to start after."
          }
        }
      },
      "FlowTriggerStart": {
        "type": "object",
        "description": "Where the saved version's trigger starts its next check.",
        "properties": {
          "flow_id": {
            "type": "string"
          },
          "version_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The saved version whose trigger this is; null when the flow was never saved."
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "from_now",
                "since_date",
                "all",
                "manual",
                "from_id"
              ]
            },
            "description": "What the trigger offers with its settings; empty when the saved version does not start with a trigger that polls."
          },
          "choice": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/FlowStartChoice"
              },
              {
                "type": "null"
              }
            ],
            "description": "What was chosen; null when nothing was, or when it was chosen for other settings of the trigger (its next check then starts From now on)."
          },
          "applies": {
            "type": "boolean",
            "description": "True while no check has committed a position since the choice, a restore from the trash or a change of the trigger's settings: the next check starts from `choice`, or From now on without one. False once a check moved past it: later checks go on from there."
          },
          "processed_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "For a trigger whose record keys hold where it read from (Priority's Watch records by changes since its last check), the last time it took a record: Since a specific date must be this time or later, so records the flow already processed never start again. Null for any other trigger, and before it took a record."
          }
        }
      },
      "FlowStartRecord": {
        "type": "object",
        "description": "A record Choose manually lists (Make's epoch item): what a person is shown, when the record was made or changed, and the record as the trigger names it, which `PUT /api/v1/flows/{id}/trigger/start` takes as `record_id` with the option `manual`.",
        "required": [
          "label",
          "date",
          "record_id"
        ],
        "properties": {
          "label": {
            "type": "string",
            "maxLength": 200,
            "description": "What the person is shown for the record, with `[redacted]` in place of any of the connection's credentials; cut at 200 characters."
          },
          "date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the record was made or changed, a UTC instant; null when the trigger gives none."
          },
          "record_id": {
            "type": "string",
            "maxLength": 1000,
            "description": "The record to start after, as the trigger names it. Never holds one of the connection's credentials: such a record is not listed."
          }
        }
      },
      "FlowRpcChoice": {
        "type": "object",
        "description": "One choice of a dropdown an app's RPC fills (Make's dynamic options RPC item): the value a run is given, and what a person is shown.",
        "required": [
          "label",
          "value"
        ],
        "properties": {
          "label": {
            "type": "string",
            "maxLength": 200,
            "description": "What the person is shown, with `[redacted]` in place of any of the connection's credentials; cut at 200 characters."
          },
          "value": {
            "oneOf": [
              {
                "type": "string",
                "minLength": 1
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              }
            ],
            "description": "What the field holds when the choice is picked. Never holds one of the connection's credentials: such a choice is not listed."
          },
          "default": {
            "type": "boolean",
            "enum": [
              true
            ],
            "description": "The choice the dropdown picks first; present only on it."
          }
        }
      },
      "FlowRpcRefused": {
        "type": "object",
        "description": "A dropdown's choices could not be read: the engine's typed refusal, its message redacted of the connection's credentials.",
        "required": [
          "error",
          "code",
          "type"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "What happened, redacted of the connection's credentials."
          },
          "code": {
            "type": "string",
            "enum": [
              "rpc_failed"
            ]
          },
          "type": {
            "type": "string",
            "description": "The engine's error class, as a run's error names it: `AccountValidationError` (the connection cannot be used: none was chosen, it was not found, was disconnected or stopped working, belongs to another app, or the system refused its credentials), `ConnectionError`, `ModuleTimeoutError` (the read took more than 40 seconds), `RateLimitError`, `DataError` or `RuntimeError`."
          },
          "retry_after_ms": {
            "type": "integer",
            "minimum": 0,
            "description": "A rate limit's: how long the system asked to wait, in milliseconds. Only when it named a wait."
          }
        }
      },
      "FlowStartRecordsRefused": {
        "type": "object",
        "description": "The trigger could not list its records: the engine's typed refusal, its message redacted of the connection's credentials.",
        "required": [
          "error",
          "code",
          "type"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "What happened, redacted of the connection's credentials."
          },
          "code": {
            "type": "string",
            "enum": [
              "start_records_failed"
            ]
          },
          "type": {
            "type": "string",
            "description": "The engine's error class, as a run's error names it: `AccountValidationError` (the connection cannot be used: none was chosen, it was disconnected or stopped working, belongs to another app, or the system refused its credentials), `ConnectionError`, `ModuleTimeoutError`, `RateLimitError`, `DataError` or `RuntimeError`."
          },
          "retry_after_ms": {
            "type": "integer",
            "minimum": 0,
            "description": "A rate limit's: how long the system asked to wait, in milliseconds. Only when it named a wait."
          }
        }
      },
      "FlowRunListItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/FlowRun"
          },
          {
            "type": "object",
            "required": [
              "flow_name",
              "run_no",
              "active_ms",
              "items",
              "started_by_name",
              "waiting_documents",
              "saved_to_retry"
            ],
            "properties": {
              "flow_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The flow's name now."
              },
              "run_no": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "The run's number in its flow; null for a run that has none."
              },
              "active_ms": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Milliseconds the run spent working, waits and sleeps left out; null when not measured."
              },
              "items": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "How many bundles the run's start handed on; null when not recorded."
              },
              "started_by_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The name of the member who started it; null when no person did, or the person is no longer a member."
              },
              "waiting_documents": {
                "type": "integer",
                "description": "The run's documents waiting for signers now."
              },
              "saved_to_retry": {
                "type": "integer",
                "description": "The run's saved runs (incomplete runs) that are pending or retrying."
              }
            }
          }
        ]
      },
      "FlowRun": {
        "type": "object",
        "description": "One run of a flow (Make: an execution).",
        "properties": {
          "id": {
            "type": "string"
          },
          "flow_id": {
            "type": "string"
          },
          "version_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The published version it ran; null when Run once ran the draft."
          },
          "draft_rev": {
            "type": [
              "integer",
              "null"
            ]
          },
          "trigger_kind": {
            "type": "string",
            "description": "`manual` for Run once; `schedule` for a run its schedule started; `instant` for a run one item of its webhook's queue started; `replay` for a replay; `retry` for a retry of an incomplete run; `call` for a run another flow's *Call a flow* module started (`parent_execution_id`), which sends its documents as its flow's activator and never counts toward the flow's errors; `api` for a run via API (`POST /flows/{id}/run`), which sends its documents as the API token's user and never counts toward the flow's errors; `module` for Run this module only (`only_module`), one module of the draft run alone, which sends its documents as the person who ran it, never counts toward the flow's errors and is never replayed."
          },
          "run_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "The name the flow's *Customize run name* module gave the run (the one furthest along in the run wins), redacted of the run's secrets and cut to 120 characters; null when nothing named it. Always null for a confidential run."
          },
          "mode": {
            "type": "string",
            "enum": [
              "real",
              "safe"
            ]
          },
          "status": {
            "type": "string",
            "description": "`running` while a bundle of the run is at work; `waiting` while every bundle still going waits for a document the run sent to be signed, declined, voided or expired (it can wait for days; Stop ends it); then `success`, `warning`, `error` or `stopped`.",
            "enum": [
              "running",
              "waiting",
              "success",
              "warning",
              "error",
              "stopped"
            ]
          },
          "confidential": {
            "type": "boolean",
            "description": "No input or output is stored."
          },
          "operations": {
            "type": "integer"
          },
          "steps": {
            "type": "integer"
          },
          "data_size": {
            "type": "integer",
            "description": "Bytes of input and output."
          },
          "replay_of": {
            "type": [
              "string",
              "null"
            ]
          },
          "retry_of": {
            "type": [
              "string",
              "null"
            ],
            "description": "A retry: the run whose incomplete run it retries."
          },
          "retry_step": {
            "type": [
              "string",
              "null"
            ],
            "description": "A retry: the step that incomplete run is kept under."
          },
          "parent_execution_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "A run another flow's *Call a flow* module started (`trigger_kind` `call`): the run that called it, in that flow. Null for any other run."
          },
          "error": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "type": {
                "type": "string",
                "description": "The error's type: a module's, such as `DataError`, or `BlueprintValidationError`, a run that could not start because its graph no longer validates (no module ran)."
              },
              "message": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "module_id": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            }
          },
          "stop_requested_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "started_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "ended_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "payloads_purged": {
            "type": "boolean"
          },
          "check_run": {
            "type": "boolean",
            "description": "A check run: a check of a trigger that polls which found nothing new to start. It started no run of its own and cannot be replayed (`not_replayable`, reason `check_run`); Run with existing data passes over it. A list can hide check runs (`check_runs=exclude`)."
          },
          "callback": {
            "type": [
              "object",
              "null"
            ],
            "description": "A run via API given a `callbackUrl` (`POST /flows/{id}/run`): what became of its one POST, never the address. Null for a run given none, and for one that could not start.",
            "required": [
              "state",
              "status_code",
              "error_type"
            ],
            "properties": {
              "state": {
                "type": "string",
                "enum": [
                  "pending",
                  "unknown",
                  "delivered",
                  "failed"
                ],
                "description": "`pending` until the run ends; `unknown` while the POST is under way, and for good when it was cut off before its outcome was recorded (it is never sent again); `delivered` once the address answered (`status_code`, any status: a 4xx or 5xx answer is not retried); `failed` when it did not answer (`error_type`)."
              },
              "status_code": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "`delivered`: the HTTP status the address answered with."
              },
              "error_type": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "RuntimeError",
                  "ConnectionError",
                  "RateLimitError",
                  "ModuleTimeoutError",
                  "DataError",
                  null
                ],
                "description": "`failed`: why. `RuntimeError` the address or a redirect was refused (or the address was lost); `ConnectionError` no answer (the network); `ModuleTimeoutError` no answer within 15 seconds; `RateLimitError` the organization's outbound limit was reached."
              }
            }
          }
        }
      },
      "FlowRunOperation": {
        "type": "object",
        "description": "One module run for one bundle.",
        "properties": {
          "step": {
            "type": "string"
          },
          "module_id": {
            "type": "integer"
          },
          "path": {
            "type": "string",
            "description": "The bundle path, e.g. `1.3`."
          },
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ]
          },
          "simulated": {
            "type": "boolean",
            "description": "A safe test ran its simulation."
          },
          "error": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "type": {
                "type": "string"
              },
              "message": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "detail": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "warnings": {
            "type": "array",
            "description": "What the module said about work it did, such as a value it cut to fit its column. A warning does not change the run's status.",
            "items": {
              "type": "string"
            }
          },
          "data_size": {
            "type": "integer"
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "ended_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the operation ended. A document's: when it was sent (its signature is not part of it). A document that settled before this change keeps the time it settled."
          },
          "settled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "A document's: when its signing ended (signed, declined, voided or expired) or its wait failed. Null for every other operation, for a document still out for signature or whose run ended first, and for a document whose signing ended before this field existed."
          },
          "has_input": {
            "type": "boolean"
          },
          "has_output": {
            "type": "boolean",
            "description": "The operation kept its own output. False for a document still out for signature: see `has_provisional_output`."
          },
          "has_provisional_output": {
            "type": "boolean",
            "description": "A document still out for signature, or whose run ended before it did: its output so far can be read with the payload route. It names the document, when it was sent and expires, and each signer's name, email, role and status as of when it was sent, and never holds a signing link. The document's real output replaces it when its signing ends. Always false in a confidential flow, which keeps no output, and for a document sent before this field existed."
          }
        }
      },
      "FlowStats": {
        "type": "object",
        "required": [
          "days",
          "since",
          "runs",
          "documents_sent",
          "documents_signed",
          "runs_without_error_pct"
        ],
        "properties": {
          "days": {
            "type": "integer"
          },
          "since": {
            "type": "string",
            "format": "date-time"
          },
          "runs": {
            "type": "integer",
            "description": "Runs in the period, safe tests and empty checks left out."
          },
          "documents_sent": {
            "type": "integer"
          },
          "documents_signed": {
            "type": "integer",
            "description": "Of the documents sent in the period, the ones signed by now."
          },
          "runs_without_error_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "The share of those runs that did not end with an error, to one decimal; null with no runs."
          }
        }
      },
      "FlowIncompleteRun": {
        "type": "object",
        "description": "An incomplete run (Make: an incomplete execution): a bundle a run could not finish, kept with its data so it can be retried. It is keyed by the run that kept it (`run_id`) and its `step`, the step a retry resumes at: the module that failed, or, for a failure inside an aggregation, the aggregation's source. `module_id` and `failed_step` name the module that failed.",
        "properties": {
          "run_id": {
            "type": "string",
            "description": "The run that kept it."
          },
          "step": {
            "type": "string",
            "description": "Where a retry resumes: `m<module>:<path>:run`, or `:agg` for an aggregator's own failure."
          },
          "module_id": {
            "type": "integer",
            "description": "The module that failed."
          },
          "failed_step": {
            "type": [
              "string",
              "null"
            ],
            "description": "The step of the module that failed."
          },
          "state": {
            "type": "string",
            "enum": [
              "pending",
              "retrying",
              "unresolved",
              "resolved"
            ],
            "description": "`pending`: never retried, or waiting for its next automatic retry (`next_retry_at`); a `pending` one with no `next_retry_at` has no automatic retry (its error has none, or its retry could not be prepared) and waits for a person, who retries or deletes it. `retrying`: a retry is going. `unresolved`: retried and failed, with no automatic retry left; a person retries or deletes it. `resolved`: a retry finished with it."
          },
          "attempts": {
            "type": "integer",
            "description": "Retries that ran, automatic or a person's."
          },
          "next_retry_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Its next automatic retry, and only once the run that kept it has ended; null when none is planned, and then a person must retry or delete it."
          },
          "superseded_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "On a resolved incomplete run whose retry kept the bundle elsewhere: the retry run whose own incomplete runs carry it on."
          },
          "error": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "retry": {
            "type": "object",
            "description": "How it is retried without a person: `break` (the Break module's own retries), `backoff` (a connection, timeout or rate-limit error: 8 retries, from a minute to 3 hours apart) or null (it waits for a person).",
            "properties": {
              "kind": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "break",
                  "backoff",
                  null
                ]
              },
              "limit": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "attempts": {
                "type": "integer",
                "description": "The retries that ran."
              },
              "interval_minutes": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "For `break`, the minutes between retries; null for `backoff`, whose waits grow (1, 10, 10, 30, 30, 30, 180, 180 minutes)."
              },
              "next_retry_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "When the next automatic retry is due, and only once the run that kept it has ended; a provider's Retry-After can push it later. Null when none is planned."
              }
            }
          },
          "parent_status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "running",
              "waiting",
              "success",
              "warning",
              "error",
              "stopped",
              null
            ],
            "description": "The status of the run that saved it. A retry waits for that run to end."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "FlowIncompleteRunDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/FlowIncompleteRun"
          },
          {
            "type": "object",
            "properties": {
              "path": {
                "type": "string",
                "description": "The bundle path of the module that failed."
              },
              "error": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  },
                  "detail": {
                    "type": "string"
                  }
                }
              },
              "last_retry_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The retry run going, or the last one."
              },
              "graph_run_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The run whose flow the next retry runs, when it is not `run_id`'s: the last retry that ran an edited mapping."
              },
              "snapshot": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "What it kept, as the run log shows values (dates and binary data tagged): where a retry resumes, the module outputs the bundle reads there, the run's variables when it failed, and the bundle's scope. Null once its run's data was purged.",
                "properties": {
                  "resume": {
                    "type": "object",
                    "properties": {
                      "module_id": {
                        "type": "integer"
                      },
                      "op": {
                        "type": "string",
                        "enum": [
                          "run",
                          "agg"
                        ]
                      },
                      "path": {
                        "type": "string"
                      }
                    }
                  },
                  "outputs": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "module_id": {
                          "type": "integer"
                        },
                        "value": {}
                      }
                    }
                  },
                  "variables": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "value": {}
                      }
                    }
                  },
                  "scope": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "description": "The modules whose outputs the bundle reads."
                  },
                  "branches": {
                    "type": "array",
                    "description": "The If-else branches the bundle took and has not merged yet.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "module_id": {
                          "type": "integer"
                        },
                        "branch": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "items": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "description": "An aggregator's own failure: the items it was given.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "path": {
                          "type": "string"
                        },
                        "scope": {
                          "type": "array",
                          "items": {
                            "type": "integer"
                          }
                        },
                        "outputs": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "module_id": {
                                "type": "integer"
                              },
                              "value": {}
                            }
                          }
                        }
                      }
                    }
                  },
                  "trigger_bundles": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "When the retry starts at the start module: how many bundles it emits again."
                  }
                }
              },
              "retries": {
                "type": "array",
                "description": "Its retries (Make: the attempts), newest first, up to 100. Each is a run of its own.",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "running",
                        "waiting",
                        "success",
                        "warning",
                        "error",
                        "stopped"
                      ]
                    },
                    "started_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "ended_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "operations": {
                      "type": "integer"
                    },
                    "automatic": {
                      "type": "boolean",
                      "description": "Started by the dispatcher, not a person."
                    },
                    "started_by": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "FlowIncompleteRunRetryRefused": {
        "type": "object",
        "description": "Why a retry cannot be prepared.",
        "required": [
          "error",
          "code"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "enum": [
              "graph_missing",
              "graph_invalid",
              "snapshot_missing",
              "invalid_map",
              "graph_too_large",
              "invalid_graph"
            ],
            "description": "`graph_missing` / `snapshot_missing`: what it needs is gone (its run's data was purged). `graph_invalid`: the flow it ran is no longer valid, or a module's connection is not the organization's, is another app's or cannot be used now (with `issues`). `invalid_map` (with `errors`), `invalid_graph` (with `issues`), `graph_too_large`: the edited mapping."
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          },
          "issues": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "truncated": {
            "type": "integer"
          }
        }
      },
      "FlowIncompleteRunRetryStarted": {
        "type": "object",
        "properties": {
          "id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The retry run; null when it could not be started and its run was never written (or, while storage is failing, could not be read)."
          },
          "status": {
            "type": "string",
            "enum": [
              "running",
              "error"
            ]
          },
          "retry_of": {
            "type": "string"
          },
          "retry_step": {
            "type": "string"
          }
        }
      },
      "FlowDocumentSigner": {
        "type": "object",
        "required": [
          "id",
          "name",
          "email",
          "role",
          "status",
          "viewed_at",
          "signed_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "role": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "The signer's status on the document, as the submission shows it."
          },
          "viewed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "signed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "FlowDocument": {
        "type": "object",
        "required": [
          "submission_id",
          "flow_id",
          "flow_name",
          "run_id",
          "run_no",
          "module_id",
          "name",
          "status",
          "sent_at",
          "expires_at",
          "signers"
        ],
        "properties": {
          "submission_id": {
            "type": "string"
          },
          "flow_id": {
            "type": "string"
          },
          "flow_name": {
            "type": "string"
          },
          "run_id": {
            "type": "string"
          },
          "run_no": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The run's number in its flow; null for a run that has none."
          },
          "module_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The document module that sent it."
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The document's name; null when the document no longer exists."
          },
          "status": {
            "type": "string",
            "enum": [
              "waiting",
              "completed",
              "declined",
              "voided",
              "expired",
              "closed"
            ],
            "description": "`waiting`: out for signature. `closed`: the run stopped waiting for it (it was stopped, or ended). Otherwise how the document ended."
          },
          "sent_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "signers": {
            "type": "array",
            "description": "In signing order.",
            "items": {
              "$ref": "#/components/schemas/FlowDocumentSigner"
            }
          }
        }
      },
      "FlowWaitSummary": {
        "type": "object",
        "required": [
          "run_id",
          "modules"
        ],
        "properties": {
          "run_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The run whose documents' outcomes are counted: the `run_id` asked for, else the flow's newest run that is not a check run; null when the flow has none."
          },
          "modules": {
            "type": "array",
            "description": "One item per document module of the flow's draft, and per other module a document names (one since deleted from the draft), in module id order.",
            "items": {
              "type": "object",
              "required": [
                "module_id",
                "waiting",
                "signed",
                "declined"
              ],
              "properties": {
                "module_id": {
                  "type": "integer"
                },
                "waiting": {
                  "type": "integer",
                  "description": "The module's documents waiting for signers now, from every run of the flow."
                },
                "signed": {
                  "type": "integer",
                  "description": "Of the run's documents from this module, the ones signed."
                },
                "declined": {
                  "type": "integer",
                  "description": "Of the run's documents from this module, the ones declined, voided or expired."
                }
              }
            }
          }
        }
      },
      "FlowAppModule": {
        "type": "object",
        "description": "A module of an app, as flow validation knows it.",
        "properties": {
          "type": {
            "type": "string",
            "description": "`<app>:<Module>`, the type a flow's module names."
          },
          "label": {
            "type": "string"
          },
          "writes": {
            "type": "boolean",
            "description": "Whether it changes something in the connected system (a safe test simulates it)."
          },
          "kind": {
            "type": "string",
            "enum": [
              "trigger",
              "action",
              "search",
              "iterator",
              "aggregator",
              "router",
              "ifelse",
              "merge",
              "directive",
              "responder",
              "document"
            ]
          },
          "canFail": {
            "type": "boolean",
            "description": "False when no error route may leave it."
          },
          "trigger": {
            "type": "string",
            "enum": [
              "webhook",
              "mailhook",
              "poll",
              "push",
              "onDemand"
            ],
            "description": "A trigger's flavour; only on triggers."
          },
          "placement": {
            "type": "string",
            "enum": [
              "webhookResponse",
              "returnOutput"
            ]
          },
          "needsIncompleteRuns": {
            "type": "boolean",
            "description": "Present (true) when the flow must store incomplete runs to use it."
          },
          "ui": {
            "$ref": "#/components/schemas/FlowModuleUi"
          },
          "makeId": {
            "type": "string",
            "description": "Make's id for the module (`builtin:Ignore`). Only on the flow-control modules, with `makeName`."
          },
          "makeName": {
            "type": "string",
            "description": "Make's name for the module (`Ignore`, `Break`), which a builder shows as a tag beside the product's own name. Only on the flow-control modules."
          }
        }
      },
      "FlowLikelyNext": {
        "type": "object",
        "required": [
          "after",
          "flows_with_after",
          "next"
        ],
        "properties": {
          "after": {
            "type": "string",
            "description": "The module type asked about."
          },
          "flows_with_after": {
            "type": "integer",
            "minimum": 0,
            "description": "How many of the organisation's flows have that module, in the draft or the published version."
          },
          "next": {
            "type": "array",
            "maxItems": 3,
            "items": {
              "type": "object",
              "required": [
                "type",
                "flows"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "description": "A module type that follows it."
                },
                "flows": {
                  "type": "integer",
                  "minimum": 1,
                  "description": "In how many flows it follows it."
                }
              }
            }
          }
        }
      },
      "FlowApp": {
        "type": "object",
        "description": "An app this organisation may use in a flow now, with its modules and this organisation's connections of it.",
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "ui": {
            "$ref": "#/components/schemas/FlowAppUi"
          },
          "status": {
            "type": "string",
            "nullable": true,
            "description": "An app pack's release status: `alpha`, `beta` or `ga`; null for the built-in apps, which every organisation has. An `off` pack is never listed."
          },
          "provider": {
            "type": "string",
            "nullable": true,
            "description": "The Connect provider of its connections (`priority`, or `pack:<app id>` for an app pack); null for an app that takes no connection."
          },
          "modules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FlowAppModule"
            }
          },
          "connections": {
            "type": "array",
            "description": "This organisation's connections of the app's provider, every status included, as `GET /api/v1/integrations/connections` shows them. Empty for an app that takes none. Present only when the caller may read Connect connections: a signed-in session, or an API token with `integrations:read` or `admin`. For any other API token the field is absent (not an empty array) on every app.",
            "items": {
              "$ref": "#/components/schemas/Connection"
            }
          }
        }
      },
      "FlowLocalized": {
        "type": "object",
        "description": "A text in both of the builder's languages: Hebrew (right to left) and English. Shown as text, never as HTML.",
        "required": [
          "en",
          "he"
        ],
        "properties": {
          "en": {
            "type": "string"
          },
          "he": {
            "type": "string"
          }
        }
      },
      "FlowAppUi": {
        "type": "object",
        "description": "How a flow builder shows an app: its name, its colour and its icon, and (each only when the app gives it) what it does, its category, how a person connects it, the steps to set it up and what its connection allows. Present only on an app that defines it. Every text is shown as text, never as HTML.",
        "required": [
          "label",
          "color",
          "icon"
        ],
        "properties": {
          "label": {
            "$ref": "#/components/schemas/FlowLocalized"
          },
          "color": {
            "type": "string",
            "description": "`#rrggbb`, shown behind white text."
          },
          "icon": {
            "type": "string",
            "description": "The key of an icon the builder bundles: `flow`, `tools`, `json`, `text`, `database`, `webhook`, `subflow`, `signflow`, `priority`, `http` or `generic`. An app pack names its own app id instead, for its logo; a builder that has no logo for that app draws the generic icon. Never a URL."
          },
          "description": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FlowLocalized"
              }
            ],
            "description": "One line on what the app does."
          },
          "category": {
            "type": "string",
            "enum": [
              "crm",
              "docs",
              "email",
              "chat",
              "sheets",
              "payments",
              "erp",
              "ai",
              "dev",
              "other"
            ],
            "description": "What kind of app it is."
          },
          "authKinds": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "signIn",
                "token",
                "credentials"
              ]
            },
            "description": "How a person connects it: signs in at the vendor (`signIn`), pastes a token or an API key (`token`), or types a user name and a password (`credentials`). Only on an app that takes a connection."
          },
          "setup": {
            "type": "object",
            "description": "The steps to get what a connection asks for (`token`), and to point the vendor at an instant trigger (`instant`).",
            "properties": {
              "token": {
                "$ref": "#/components/schemas/FlowSetupGuide"
              },
              "instant": {
                "$ref": "#/components/schemas/FlowSetupGuide"
              }
            }
          },
          "scopeWords": {
            "type": "object",
            "description": "What the app's connection lets the product do (`can`), and what it never does (`cannot`).",
            "required": [
              "can",
              "cannot"
            ],
            "properties": {
              "can": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FlowLocalized"
                }
              },
              "cannot": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FlowLocalized"
                }
              }
            }
          }
        }
      },
      "FlowSetupGuide": {
        "type": "object",
        "description": "Steps a person follows, in order, and the vendor's page for them.",
        "required": [
          "steps"
        ],
        "properties": {
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FlowLocalized"
            },
            "description": "1 to 10 steps."
          },
          "link": {
            "type": "string",
            "format": "uri",
            "description": "An https address of the vendor's, never with a user name or password in it."
          }
        }
      },
      "FlowNoun": {
        "type": "object",
        "description": "A noun in the singular (`one`) and the plural (`other`).",
        "required": [
          "one",
          "other"
        ],
        "properties": {
          "one": {
            "type": "string"
          },
          "other": {
            "type": "string"
          }
        }
      },
      "FlowItemNoun": {
        "type": "object",
        "description": "What a trigger's items are called, in each language (\"customer\", \"customers\"). A builder that finds none says \"item\".",
        "required": [
          "en",
          "he"
        ],
        "properties": {
          "en": {
            "$ref": "#/components/schemas/FlowNoun"
          },
          "he": {
            "$ref": "#/components/schemas/FlowNoun"
          }
        }
      },
      "FlowModuleUi": {
        "type": "object",
        "description": "How a flow builder shows a module: its name and a short description. Present only on a module that defines it.",
        "properties": {
          "label": {
            "$ref": "#/components/schemas/FlowLocalized"
          },
          "description": {
            "$ref": "#/components/schemas/FlowLocalized"
          },
          "hidden": {
            "type": "boolean",
            "description": "True: the module is withheld from the modules a builder offers, so `GET /api/v1/flows/apps` never lists it (and so never shows this field true); a flow that has one still validates and runs, and `POST /api/v1/flows/schemas` still answers its forms."
          },
          "outcome": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FlowLocalized"
              }
            ],
            "description": "An error handler's one line on how the run ends when it handles an error."
          },
          "itemNoun": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FlowItemNoun"
              }
            ],
            "description": "On a trigger: what its items are called."
          },
          "twin": {
            "type": "string",
            "description": "A trigger's twin: the same app's trigger in the other mode (polling and instant), so the Trigger tab can offer to switch between them."
          },
          "pickerGroup": {
            "type": "string",
            "enum": [
              "flowControl"
            ],
            "description": "`flowControl`: a builder pins the module in its Flow control choices (Router, If-else, Iterator, an aggregator, Set variable, Sleep). Absent on every other module."
          }
        }
      },
      "FlowSourceRef": {
        "type": "object",
        "description": "Where a builder lists a field's choices from, through this organisation's own routes: a kind of list, never a URL or a query.",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "connection.objects",
              "connection.fields",
              "connection.targets",
              "connection.records",
              "org.templates",
              "org.templateFields",
              "org.templateRoles",
              "org.dataStructures",
              "org.dataStores",
              "org.flows",
              "pack.rpc"
            ]
          },
          "params": {
            "type": "object",
            "description": "The list's parameters, each naming the sibling field that holds its value.",
            "additionalProperties": {
              "type": "string"
            }
          },
          "rpc": {
            "type": "string",
            "description": "`pack.rpc` only: the module's own `rpc://` reference, exactly as its app wrote it, which `POST /api/v1/flows/{id}/rpc` takes as `rpc` (`POST /api/v1/flows/{id}/rpc-fields` for fetched fields and outputs). Its `params` are the fields the reference and its RPC read, each by its own name."
          }
        }
      },
      "FlowFieldUi": {
        "type": "object",
        "description": "How a flow builder shows a field or a parameter. Never read while a flow runs: a default is what the builder puts in a new module's form, and options are what it offers, never a check of what is mapped.",
        "properties": {
          "label": {
            "$ref": "#/components/schemas/FlowLocalized"
          },
          "help": {
            "$ref": "#/components/schemas/FlowLocalized"
          },
          "placeholder": {
            "$ref": "#/components/schemas/FlowLocalized"
          },
          "banner": {
            "$ref": "#/components/schemas/FlowLocalized"
          },
          "options": {
            "type": "array",
            "description": "A fixed list of choices.",
            "items": {
              "type": "object",
              "required": [
                "value",
                "label"
              ],
              "properties": {
                "value": {
                  "description": "Text, a number or a boolean."
                },
                "label": {
                  "$ref": "#/components/schemas/FlowLocalized"
                }
              }
            }
          },
          "advanced": {
            "type": "boolean",
            "description": "Shown only among the advanced settings."
          },
          "default": {
            "description": "What the builder puts in a new module's form."
          },
          "multiline": {
            "type": "boolean"
          },
          "nested": {
            "type": "object",
            "description": "The fields shown under this one while it holds a value, by that value as text.",
            "additionalProperties": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/FlowFieldSpec"
              }
            }
          },
          "source": {
            "$ref": "#/components/schemas/FlowSourceRef"
          },
          "map": {
            "type": "string",
            "enum": [
              "never",
              "allow"
            ],
            "description": "`never`: the builder offers no mapping for the field."
          },
          "addLabel": {
            "$ref": "#/components/schemas/FlowLocalized"
          },
          "erase": {
            "type": "string",
            "enum": [
              "refused"
            ],
            "description": "`refused` when a run refuses the `erase` keyword in this mapped input: the connected system does not let it be cleared (a Priority number, amount, date or yes/no column). The builder marks erase there."
          },
          "section": {
            "$ref": "#/components/schemas/FlowLocalized"
          },
          "series": {
            "type": "object",
            "description": "On a switch whose nested fields are three numbers: how a flow builder says the plan they make (a first step, then every so often, up to a count). The sentences carry `{count}` and `{days}`. Builder metadata only.",
            "required": [
              "first",
              "every",
              "count",
              "unit",
              "sentence",
              "sentenceOne"
            ],
            "properties": {
              "first": {
                "type": "string",
                "description": "The name of the nested field holding the first step."
              },
              "every": {
                "type": "string",
                "description": "The name of the nested field holding the step between two."
              },
              "count": {
                "type": "string",
                "description": "The name of the nested field holding how many."
              },
              "unit": {
                "type": "object",
                "required": [
                  "one",
                  "other"
                ],
                "properties": {
                  "one": {
                    "$ref": "#/components/schemas/FlowLocalized"
                  },
                  "other": {
                    "$ref": "#/components/schemas/FlowLocalized"
                  }
                }
              },
              "sentence": {
                "$ref": "#/components/schemas/FlowLocalized"
              },
              "sentenceOne": {
                "$ref": "#/components/schemas/FlowLocalized"
              }
            }
          },
          "nestedAny": {
            "type": "array",
            "description": "The fields shown under this select while it holds any value (Make's options-level `nested`).",
            "items": {
              "$ref": "#/components/schemas/FlowFieldSpec"
            }
          },
          "nestedSource": {
            "description": "Where the builder fetches the fields shown under this select while it holds any value (Make's dynamic fields RPC): a `pack.rpc` source, asked of `POST /flows/{id}/rpc-fields`.",
            "allOf": [
              {
                "$ref": "#/components/schemas/FlowSourceRef"
              }
            ]
          },
          "specSource": {
            "description": "Where the builder fetches this collection's own fields (Make's dynamic fields RPC): a `pack.rpc` source, asked of `POST /flows/{id}/rpc-fields`.",
            "allOf": [
              {
                "$ref": "#/components/schemas/FlowSourceRef"
              }
            ]
          }
        }
      },
      "FlowFieldItem": {
        "type": "object",
        "description": "The item of an array whose items are plain values (text, numbers, dates and the like), not collections: their type, and how a flow builder shows each one. It names nothing and has no fields of its own: an array of collections lists their fields in its `spec`. Never read while a flow runs.",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "text",
              "number",
              "boolean",
              "date",
              "array",
              "collection",
              "buffer",
              "any"
            ],
            "description": "The type of each item. An array of collections describes their fields with `spec` instead."
          },
          "label": {
            "type": "string",
            "description": "The app's own title for each item."
          },
          "ui": {
            "$ref": "#/components/schemas/FlowFieldUi"
          }
        }
      },
      "FlowFieldSpec": {
        "type": "object",
        "description": "A field a module reads or gives.",
        "required": [
          "name",
          "type"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "text",
              "number",
              "boolean",
              "date",
              "array",
              "collection",
              "buffer",
              "any"
            ]
          },
          "required": {
            "type": "boolean"
          },
          "spec": {
            "type": "array",
            "description": "The fields of a collection, or, on an array, the fields of each item: its items are collections. An array of plain values says so with `item` instead.",
            "items": {
              "$ref": "#/components/schemas/FlowFieldSpec"
            }
          },
          "item": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FlowFieldItem"
              }
            ],
            "description": "On an array only: its items are plain values of this type. An array has at most one of `spec` and `item`; with neither, its items may be anything."
          },
          "label": {
            "type": "string",
            "description": "The app's own title for the field; absent: the name."
          },
          "refersTo": {
            "type": "string",
            "enum": [
              "dataStructure",
              "dataStore"
            ],
            "description": "The setting names one of this organisation's records by its id."
          },
          "ui": {
            "$ref": "#/components/schemas/FlowFieldUi"
          }
        }
      },
      "FlowParamSpec": {
        "type": "object",
        "description": "A mapped input of a module, as a run checks it.",
        "required": [
          "name",
          "type"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "text",
              "number",
              "boolean",
              "date",
              "array",
              "collection",
              "buffer",
              "any"
            ]
          },
          "required": {
            "type": "boolean"
          },
          "min": {
            "type": "number"
          },
          "max": {
            "type": "number"
          },
          "label": {
            "type": "string",
            "description": "The app's own title for the input; absent: the name."
          },
          "ui": {
            "$ref": "#/components/schemas/FlowFieldUi"
          },
          "spec": {
            "type": "array",
            "description": "The fields inside an input that is a collection, or inside each item of an input that is an array, whose items are then collections, each with its own `ui`: what a flow builder shows inside the input. An array of plain values says so with `item` instead. Never read while a flow runs: the engine's coercion of a mapped input checks only the input's own type; a module may still check what is inside the value it receives.",
            "items": {
              "$ref": "#/components/schemas/FlowFieldSpec"
            }
          },
          "item": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FlowFieldItem"
              }
            ],
            "description": "On an input that is an array only: its items are plain values of this type. An array has at most one of `spec` and `item`; with neither, its items may be anything. Never read while a flow runs: an array keeps its items as given."
          }
        }
      },
      "FlowNodeSchemas": {
        "type": "object",
        "description": "A module's forms for one node: its settings, its mapped inputs for those settings, and its output.",
        "required": [
          "config",
          "input",
          "output"
        ],
        "properties": {
          "config": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FlowFieldSpec"
            }
          },
          "input": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FlowParamSpec"
            }
          },
          "output": {
            "type": "object",
            "required": [
              "fields"
            ],
            "properties": {
              "fields": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FlowFieldSpec"
                }
              },
              "open": {
                "type": "boolean",
                "description": "True: the output may carry other fields too."
              },
              "source": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/FlowSourceRef"
                  }
                ],
                "description": "GAP-10 (Make's dynamic interface): where the builder fetches this output's fields, a `pack.rpc` source whose `params` name the module's own settings and inputs (`POST /api/v1/flows/{id}/rpc-fields` with `use` \"outputs\"). The output is then `open`, so a mapping into what it fetches is never unknown; a run never reads it, and the builder never saves what it fetches into the flow."
              }
            }
          }
        }
      },
      "FlowNodeSchemaError": {
        "type": "object",
        "description": "Why a node has no schemas.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "unknown_module_type",
                  "invalid_node"
                ],
                "description": "`unknown_module_type`: a type this organisation may not use. `invalid_node`: the node does not parse, or its module cannot give its schemas for its settings."
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "FlowAppAccess": {
        "type": "object",
        "description": "Which app packs in early release an organisation sees, as the platform owner set it.",
        "properties": {
          "organization_id": {
            "type": "string"
          },
          "alpha": {
            "type": "boolean",
            "description": "The organisation sees every `alpha` app pack."
          },
          "apps": {
            "type": "array",
            "description": "The `beta` app packs it was enabled for, by app id.",
            "items": {
              "type": "object",
              "properties": {
                "app_id": {
                  "type": "string"
                },
                "enabled_by": {
                  "type": "string",
                  "nullable": true,
                  "description": "The user who enabled it."
                },
                "enabled_at": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          }
        }
      },
      "SetFlowAppsAlphaInput": {
        "type": "object",
        "required": [
          "enabled"
        ],
        "properties": {
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "FlowAppsAlphaChange": {
        "type": "object",
        "properties": {
          "organization_id": {
            "type": "string"
          },
          "alpha": {
            "type": "boolean"
          },
          "changed": {
            "type": "boolean",
            "description": "False when the flag already had this value: nothing was written or audited."
          }
        }
      },
      "FlowAppAccessChange": {
        "type": "object",
        "properties": {
          "organization_id": {
            "type": "string"
          },
          "app_id": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "changed": {
            "type": "boolean",
            "description": "False when the organisation already had (or did not have) this access: nothing was written or audited."
          }
        }
      },
      "SetFlowEngineInput": {
        "type": "object",
        "required": [
          "enabled"
        ],
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "True turns the flow engine on for the organisation, false turns it off."
          }
        }
      },
      "FlowEngineChange": {
        "type": "object",
        "properties": {
          "organization_id": {
            "type": "string"
          },
          "flow_engine": {
            "type": "boolean",
            "description": "Whether the organisation now has the flow engine."
          },
          "changed": {
            "type": "boolean",
            "description": "False when the flow engine was already on (or off): nothing was written or audited."
          }
        }
      },
      "OAuthClientEntry": {
        "type": "object",
        "description": "An app pack that connects with the OAuth client LeonAI registered with its provider (by a sign-in at the provider's browser page, or with the client alone), and the OAuth client the platform owner set for it. Never the client secret, nor any part of it: only whether one is set.",
        "properties": {
          "packId": {
            "type": "string",
            "description": "The app pack's id."
          },
          "label": {
            "type": "string",
            "description": "The app's name (its id for a client whose pack no longer takes one)."
          },
          "available": {
            "type": "boolean",
            "description": "False for a client left for a pack that no longer takes one, or is no longer offered: it is listed so it can be removed."
          },
          "clientId": {
            "type": "string",
            "nullable": true,
            "description": "The OAuth client id, or null when none is set."
          },
          "secretSet": {
            "type": "boolean",
            "description": "Whether a client secret is set."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the client was last set or rotated."
          },
          "updatedBy": {
            "type": "string",
            "nullable": true,
            "description": "The user who last set or rotated it."
          }
        }
      },
      "SetOAuthClientInput": {
        "type": "object",
        "required": [
          "clientId",
          "clientSecret"
        ],
        "properties": {
          "clientId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512,
            "pattern": "^[\\x21-\\x7e](?:[\\x20-\\x7e]{0,510}[\\x21-\\x7e])?$",
            "description": "The OAuth client id LeonAI registered with the provider: printable ASCII (RFC 6749 VSCHAR), with no space at either end."
          },
          "clientSecret": {
            "type": "string",
            "format": "password",
            "writeOnly": true,
            "minLength": 1,
            "maxLength": 4096,
            "description": "The client secret. Stored encrypted and never returned."
          }
        }
      },
      "OAuthClientChange": {
        "allOf": [
          {
            "$ref": "#/components/schemas/OAuthClientEntry"
          },
          {
            "type": "object",
            "properties": {
              "action": {
                "type": "string",
                "enum": [
                  "created",
                  "rotated"
                ],
                "description": "`created` when the pack had no client, `rotated` when this replaced one."
              }
            }
          }
        ]
      },
      "OAuthClientDeletion": {
        "type": "object",
        "properties": {
          "packId": {
            "type": "string"
          },
          "changed": {
            "type": "boolean",
            "description": "False when the pack had no client: nothing was removed or audited."
          }
        }
      }
    }
  }
}
