{
  "openapi": "3.1.0",
  "info": {
    "title": "WebhookVault API",
    "version": "1.0.0",
    "description": "The WebhookVault REST API: create capture endpoints, read and search what they captured, replay deliveries, and drive CI flows with ephemeral endpoints and the await API.\n\nEvery response body is JSON. Every error is an RFC 9457 problem-details object with a stable machine-readable `code`: parse `code`, display `detail`.",
    "contact": {
      "email": "support@webhookvault.net"
    }
  },
  "servers": [
    {
      "url": "https://app.webhookvault.net",
      "description": "Production"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Introspection",
      "description": "Who am I: key, workspace, plan, limits."
    },
    {
      "name": "Endpoints",
      "description": "Capture endpoints: the URLs that receive and store webhooks."
    },
    {
      "name": "Requests",
      "description": "Captured requests: list, search, read, delete."
    },
    {
      "name": "Replay",
      "description": "Re-deliver captured requests to the endpoint's forward destination."
    },
    {
      "name": "CI",
      "description": "Testing mode: ephemeral endpoints and the block-until-a-request-arrives await API."
    },
    {
      "name": "Actions",
      "description": "What happens to a capture: forward it to a URL, or notify through an alert channel. An endpoint may have several and they do not chain - each sees the original request."
    },
    {
      "name": "Transformations",
      "description": "Declarative rules that shape what a forward destination receives: an ordered set of JSON documents (no code), each with its own condition, versioned, previewable, written by hand, in the app, or by an LLM from the published spec."
    },
    {
      "name": "Deliveries",
      "description": "Every delivery attempt on an endpoint, and for each one exactly what left the vault (method, URL, headers, body) and exactly what came back (status, headers, body)."
    },
    {
      "name": "Recycle bin",
      "description": "Deleted captured requests. A DELETE moves a request here rather than removing it; it stays restorable for 7 days, then the retention sweep removes it. Only that sweep or a purge applied by WebhookVault support deletes a request for good."
    },
    {
      "name": "Alert channels",
      "description": "Where this workspace's alerts go, and the record of what was sent. Credentials are write-only: no route returns one, and a channel reports only whether a credential is set and when."
    },
    {
      "name": "Watchdogs",
      "description": "Dead-man's switches on your endpoints. A watchdog expects a matching capture at least every N, with G of grace; miss the window and it alarms through your alert channels. The one thing you cannot detect by polling us, because the symptom is that nothing arrives."
    }
  ],
  "paths": {
    "/api/v1/me": {
      "get": {
        "tags": [
          "Introspection"
        ],
        "operationId": "getMe",
        "summary": "Introspect the key",
        "description": "Returns the authenticated key's identity, its workspace, the plan, and the effective limits. The first call every integration should make. It proves the key works and tells you what you're allowed to do. Never echoes the key itself.",
        "responses": {
          "200": {
            "description": "Key, workspace, plan and limits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/providers": {
      "get": {
        "tags": [
          "Providers"
        ],
        "operationId": "listProviders",
        "summary": "List the provider catalog",
        "description": "The webhook providers WebhookVault understands: the slug an endpoint's `provider` field accepts, where each provider announces its event type, which header carries its signature, and whether subscribing requires a challenge handshake. Static reference data: cache it freely.",
        "responses": {
          "200": {
            "description": "Every known provider.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints": {
      "get": {
        "tags": [
          "Endpoints"
        ],
        "operationId": "listEndpoints",
        "summary": "List endpoints",
        "description": "All endpoints in the key's workspace, newest first.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/pageSize"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EndpointPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Endpoints"
        ],
        "operationId": "createEndpoint",
        "summary": "Create an endpoint",
        "description": "Creates a capture endpoint and returns it, including its capture `url`. Send webhooks to that URL immediately, with no further setup.\n\nPass `ttlSeconds` (60–86400) to create an **ephemeral endpoint** for CI: it answers 404 after expiry and is deleted, together with everything it captured, by the retention sweep. Endpoint creation counts against the plan's endpoint limit either way.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateEndpoint"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The endpoint, with its capture URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Endpoint"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        }
      ],
      "get": {
        "tags": [
          "Endpoints"
        ],
        "operationId": "getEndpoint",
        "summary": "Get an endpoint",
        "responses": {
          "200": {
            "description": "The endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Endpoint"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "tags": [
          "Endpoints"
        ],
        "operationId": "updateEndpoint",
        "summary": "Update an endpoint",
        "description": "Partial update: omitted fields stay as they are. Two field-specific rules: `forwardUrl` omitted/null = unchanged, empty string = cleared; `responseHeaders` as an empty object `{}` = cleared.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateEndpoint"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Endpoint"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`code: concurrent_change` - one of the endpoint's actions was removed by another request while this update was saving. Nothing was saved; read the endpoint again and retry.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Endpoints"
        ],
        "operationId": "deleteEndpoint",
        "summary": "Delete an endpoint",
        "description": "Deletes the endpoint and every request it stored. Senders get 404 immediately. This cannot be undone.",
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/transformations": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        }
      ],
      "get": {
        "tags": [
          "Transformations"
        ],
        "operationId": "getTransformationSet",
        "summary": "The endpoint's transformation set",
        "description": "Every rule on the endpoint's forward destination in the order it runs (`rules` is empty when none is set), the set limit, and whether the plan includes transformations. Each delivery runs the enabled rules top to bottom; a rule applies when its own `when` matches and sees the result of the rules before it.",
        "responses": {
          "200": {
            "description": "The set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransformationSet"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Transformations"
        ],
        "operationId": "createTransformation",
        "summary": "Add a rule to the end of the set",
        "description": "The body is the rule document itself (see `GET /api/v1/transformations/schema` and https://app.webhookvault.net/llms-transformations.txt). Validated first: every error is listed in the problem's `errors` extension. Needs a plan with transformations (Pro and up) and a forward destination. The new rule is version 1 and runs last; reorder with `PUT …/transformations/order`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransformationRule"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The rule as stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransformationRuleView"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`code: no_destination` (set a forward destination first) or `code: limit_reached` (20 rules per destination).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/transformations/order": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        }
      ],
      "put": {
        "tags": [
          "Transformations"
        ],
        "operationId": "reorderTransformations",
        "summary": "Reorder the set",
        "description": "`order` lists every rule id of the set exactly once, in the order they should run.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "order"
                ],
                "properties": {
                  "order": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The reordered set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransformationSet"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/transformations/preview-set": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        }
      ],
      "post": {
        "tags": [
          "Transformations"
        ],
        "operationId": "previewTransformationSet",
        "summary": "Run the saved set against a saved request",
        "description": "The whole current set, enabled rules in order, exactly as the dispatcher would run it. `note` names every rule and version with what it did.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sampleRequestId"
                ],
                "properties": {
                  "sampleRequestId": {
                    "type": "integer",
                    "format": "int64"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The delivery after the set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransformationPreview"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/transformations/{ruleId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        },
        {
          "$ref": "#/components/parameters/ruleId"
        }
      ],
      "get": {
        "tags": [
          "Transformations"
        ],
        "operationId": "getTransformationRule",
        "summary": "One rule of the set",
        "responses": {
          "200": {
            "description": "The rule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransformationRuleView"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Transformations"
        ],
        "operationId": "updateTransformationRule",
        "summary": "Replace a rule's document and/or switch it on or off",
        "description": "Send `rule` (the document; a changed document is saved as the next version, an identical one is not), `enabled`, or both. Disabling keeps the rule in the set; the dispatcher skips it and every attempt says so.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransformationUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rule as stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransformationRuleView"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Transformations"
        ],
        "operationId": "deleteTransformationRule",
        "summary": "Remove a rule and its history",
        "description": "The rules after it move up. Deliveries from the next attempt on run the remaining rules only.",
        "responses": {
          "204": {
            "description": "Removed."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/transformations/{ruleId}/versions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        },
        {
          "$ref": "#/components/parameters/ruleId"
        }
      ],
      "get": {
        "tags": [
          "Transformations"
        ],
        "operationId": "listTransformationVersions",
        "summary": "Every saved version of a rule, newest first",
        "responses": {
          "200": {
            "description": "The versions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TransformationVersion"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/transformations/{ruleId}/versions/{version}/restore": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        },
        {
          "$ref": "#/components/parameters/ruleId"
        },
        {
          "name": "version",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "post": {
        "tags": [
          "Transformations"
        ],
        "operationId": "restoreTransformationVersion",
        "summary": "Restore a version",
        "description": "Saves that version's document as the NEXT version. History is never rewritten; the version that was current stays in it.",
        "responses": {
          "200": {
            "description": "The rule as stored (new version).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransformationRuleView"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/recycle-bin": {
      "get": {
        "tags": [
          "Recycle bin"
        ],
        "operationId": "listRecycleBin",
        "summary": "Deleted requests waiting in the recycle bin",
        "description": "Every request deleted from this workspace's endpoints and still restorable, newest deletion first. `purgesAt` is when the retention sweep removes it for good: the bin's 7-day window, or the end of your plan's retention window measured from when the request arrived, whichever comes first.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of deleted requests.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/RecycleBinItem"
                      }
                    },
                    "totalCount": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "pageSize": {
                      "type": "integer"
                    },
                    "retentionDays": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/recycle-bin/restore": {
      "post": {
        "tags": [
          "Recycle bin"
        ],
        "operationId": "restoreFromRecycleBin",
        "summary": "Restore deleted requests",
        "description": "Puts the named requests back on their endpoints, newest first. Ids that are not in this workspace's bin are ignored. A request is left in the bin when its endpoint is already at the plan's stored-request limit, because restoring past the limit would only hand it to the next capture's eviction, which is permanent: `skippedAtCap` counts those.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ids"
                ],
                "properties": {
                  "ids": {
                    "type": "array",
                    "maxItems": 500,
                    "items": {
                      "type": "integer",
                      "format": "int64"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many were restored, and how many stayed in the bin at the plan limit.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "restoredCount": {
                      "type": "integer"
                    },
                    "skippedAtCap": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/deliveries": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        },
        {
          "name": "page",
          "in": "query",
          "schema": {
            "type": "integer",
            "default": 1
          }
        },
        {
          "name": "pageSize",
          "in": "query",
          "schema": {
            "type": "integer",
            "default": 50,
            "maximum": 100
          }
        },
        {
          "name": "state",
          "in": "query",
          "schema": {
            "type": "string",
            "enum": [
              "Succeeded",
              "Failed",
              "Errored",
              "DeadLetter",
              "Pending",
              "Interrupted"
            ]
          },
          "description": "Outcome filter. `Pending` is an attempt still inside the dispatcher's visibility window with no result yet; `Interrupted` is one past it (the worker died mid-send and the job was re-queued)."
        },
        {
          "name": "requestId",
          "in": "query",
          "schema": {
            "type": "integer",
            "format": "int64"
          },
          "description": "Only attempts for this captured request."
        }
      ],
      "get": {
        "tags": [
          "Deliveries"
        ],
        "operationId": "listDeliveries",
        "summary": "Delivery attempts on the endpoint",
        "description": "Every attempt, newest first, across every request: automatic retries and replays alike. `hasDetail` says whether the full record (headers and bodies both ways) exists; attempts made before that record was kept show the outcome only.",
        "responses": {
          "200": {
            "description": "A page of attempts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Delivery"
                      }
                    },
                    "totalCount": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "pageSize": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/deliveries/{attemptId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        },
        {
          "name": "attemptId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer",
            "format": "int64"
          }
        }
      ],
      "get": {
        "tags": [
          "Deliveries"
        ],
        "operationId": "getDelivery",
        "summary": "One delivery attempt in full",
        "description": "Exactly what was sent (method, URL, every header after transformation, the body) and exactly what came back (status, every header, the body, content type). Bodies are stored up to 256 KB and flagged `bodyTruncated` when cut; a binary body comes back base64 with `bodyIsBinary`.",
        "responses": {
          "200": {
            "description": "The attempt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeliveryDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/transformation": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        }
      ],
      "get": {
        "tags": [
          "Transformations"
        ],
        "operationId": "getTransformation",
        "summary": "Compatibility: the first rule of the set",
        "description": "Kept for integrations written against the single-rule API. Addresses the rule at position 0 of the set (`rule` is null when the set is empty); `ruleCount` says how many rules the set holds. New integrations use `…/transformations`.",
        "responses": {
          "200": {
            "description": "The rule view.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransformationView"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Transformations"
        ],
        "operationId": "putTransformation",
        "summary": "Compatibility: create or replace the first rule",
        "description": "Creates the set's first rule when it is empty, otherwise replaces the document of the rule at position 0 (a new version). Other rules in the set are untouched. The body is the rule document itself.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransformationRule"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved rule view (version incremented).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransformationView"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The endpoint has no forward destination yet (`code: no_destination`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Transformations"
        ],
        "operationId": "deleteTransformation",
        "summary": "Compatibility: remove the first rule",
        "description": "Removes the rule at position 0 of the set and its history; other rules stay. Idempotent.",
        "responses": {
          "204": {
            "description": "Removed (or there was nothing to remove)."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/transformations/schema": {
      "get": {
        "tags": [
          "Transformations"
        ],
        "operationId": "getTransformationSchema",
        "summary": "The rule document's JSON Schema",
        "description": "JSON Schema (2020-12) for the v1 rule grammar. Feed it to a model or a validator; the same rules are enforced on save and preview.",
        "responses": {
          "200": {
            "description": "The schema.",
            "content": {
              "application/schema+json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/transformations/preview": {
      "post": {
        "tags": [
          "Transformations"
        ],
        "operationId": "previewTransformation",
        "summary": "Preview a rule against a sample",
        "description": "Validates the rule, then runs it against a saved request (`sampleRequestId`, any endpoint in your workspace) or an inline `sampleRequest`. Returns the delivery as it would go out plus a note per step. Nothing is saved; available on every plan, so a rule can be built before upgrading. This is the iteration loop for an LLM: validate, preview, adjust, then PUT.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransformationPreviewRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The transformed delivery and per-step notes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransformationPreview"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/stats": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        }
      ],
      "get": {
        "tags": [
          "Endpoints"
        ],
        "operationId": "getEndpointStats",
        "summary": "Endpoint stats",
        "description": "Operational snapshot: stored-request count, last traffic, retention window, and the delivery backlog (queued and dead-lettered jobs). Built for CI checks and monitoring probes.",
        "responses": {
          "200": {
            "description": "The snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EndpointStats"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/requests": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        }
      ],
      "get": {
        "tags": [
          "Requests"
        ],
        "operationId": "listRequests",
        "summary": "List and search requests",
        "description": "Captured requests, newest first, with search-lite filters, all combined with AND.\n\n`q` matches the path and query string as a case-insensitive substring on every plan; on plans with full payload search it also matches non-binary request bodies.",
        "parameters": [
          {
            "$ref": "#/components/parameters/method"
          },
          {
            "$ref": "#/components/parameters/state"
          },
          {
            "$ref": "#/components/parameters/verification"
          },
          {
            "$ref": "#/components/parameters/q"
          },
          {
            "name": "since",
            "in": "query",
            "description": "Only requests received at or after this instant (ISO 8601, UTC).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "until",
            "in": "query",
            "description": "Only requests received before this instant (ISO 8601, UTC).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "afterId",
            "in": "query",
            "description": "Only requests with an id greater than this.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/pageSize"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of captured requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Requests"
        ],
        "operationId": "clearRequests",
        "summary": "Delete all requests (moves them to the recycle bin)",
        "description": "Moves every saved request on the endpoint to the workspace's recycle bin, where it can be restored for 7 days. The endpoint itself stays up and keeps capturing.",
        "responses": {
          "200": {
            "description": "How many were deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedCount"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/requests/await": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        }
      ],
      "get": {
        "tags": [
          "CI"
        ],
        "operationId": "awaitRequest",
        "summary": "Await a request",
        "description": "Long-poll: blocks until a request matching the filters arrives, then returns it (200). If nothing matching arrives within `timeoutSeconds`, returns **204 No Content**. Retry or fail your check.\n\nBy default only requests arriving *after* this call starts match. That leaves a race if the webhook can fire before your await begins: close it by reading the newest stored id first and passing it as `afterId` (or pass `afterId=0` to accept anything already stored).\n\nTypical CI shape:\n1. Create an ephemeral endpoint (`ttlSeconds`).\n2. Point the system under test at its capture `url`.\n3. Trigger the action.\n4. `GET …/requests/await?q=order.created&timeoutSeconds=60` and assert on the body.",
        "parameters": [
          {
            "$ref": "#/components/parameters/method"
          },
          {
            "$ref": "#/components/parameters/state"
          },
          {
            "$ref": "#/components/parameters/verification"
          },
          {
            "$ref": "#/components/parameters/q"
          },
          {
            "name": "since",
            "in": "query",
            "description": "Match requests received at or after this instant instead of only future ones.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "afterId",
            "in": "query",
            "description": "Match requests with an id greater than this instead of only future ones. Pass the newest id you've already seen, or 0 to accept anything stored.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "timeoutSeconds",
            "in": "query",
            "description": "How long to block. 1–80; default 30.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 80,
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A matching request arrived.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CapturedRequest"
                }
              }
            }
          },
          "204": {
            "description": "Nothing matching arrived within the timeout."
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/requests/{requestId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        },
        {
          "$ref": "#/components/parameters/requestId"
        }
      ],
      "get": {
        "tags": [
          "Requests"
        ],
        "operationId": "getRequest",
        "summary": "Get a request",
        "description": "One captured request in full: headers as an object, the body (base64-encoded when `bodyIsBinary` is true), and the latest delivery outcome.",
        "responses": {
          "200": {
            "description": "The captured request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CapturedRequest"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Requests"
        ],
        "operationId": "deleteRequest",
        "summary": "Delete a request (moves it to the recycle bin)",
        "description": "Moves the request to the workspace's recycle bin, where it can be restored for up to 7 days, or until the plan's retention window ends, whichever comes first. Only the retention sweep or a purge applied by WebhookVault support removes it for good.",
        "responses": {
          "204": {
            "description": "Moved to the recycle bin."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/requests/{requestId}/replay": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        },
        {
          "$ref": "#/components/parameters/requestId"
        }
      ],
      "post": {
        "tags": [
          "Replay"
        ],
        "operationId": "replayRequest",
        "summary": "Replay a request",
        "description": "Re-sends the captured request through the endpoint's actions and waits for the outcome. The attempt is recorded in the delivery history like any other. Requires an action to be switched on.\n\nBy default the request goes to every action that is switched on. Pass `actionId` to replay it through that one action only: the other actions send nothing. The action must belong to this endpoint (an action of any other endpoint answers 404, the same as one that never existed) and must be switched on (`code: action_inactive` otherwise).",
        "parameters": [
          {
            "name": "actionId",
            "in": "query",
            "required": false,
            "description": "Optional. The one action (from `GET /api/v1/endpoints/:endpointId/actions`) to replay the request through. Omit it to replay to every action that is switched on.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The delivery outcome of this replay.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReplayOutcome"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input (`code: validation_failed`), no action is switched on (`code: forwarding_disabled`), or the chosen action is switched off (`code: action_inactive`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "The endpoint or the request does not exist in this workspace, or the chosen action is not one of this endpoint's actions (`code: not_found`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/requests/{requestId}/attempts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        },
        {
          "$ref": "#/components/parameters/requestId"
        }
      ],
      "get": {
        "tags": [
          "Replay"
        ],
        "operationId": "listAttempts",
        "summary": "Delivery attempts",
        "description": "The full delivery history for one captured request: automatic retries and manual replays alike, newest first (up to 100). An attempt with no completion is labelled `Interrupted`: a worker died mid-send and the delivery was retried.",
        "responses": {
          "200": {
            "description": "The attempts, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DeliveryAttempt"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/requests/bulk-replay": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        }
      ],
      "post": {
        "tags": [
          "Replay"
        ],
        "operationId": "bulkReplay",
        "summary": "Bulk replay",
        "description": "Queues up to 500 captured requests for re-delivery and returns immediately; deliveries run in the background under the endpoint's rate gate, and each lands in the delivery history. Requests whose stored body was an oversize placeholder are skipped (`skippedTruncated`), because replaying a placeholder would lie to the target.\n\nBy default each request goes to every action that is switched on. Send `actionId` to queue one job per request for that action only. The action must belong to this endpoint (404 otherwise) and be switched on (`code: action_inactive` otherwise).\n\nPlan-gated: requires the bulk replay feature.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkReplay"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was queued and what was skipped.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkReplayOutcome"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input (`code: validation_failed`), no action is switched on (`code: forwarding_disabled`), or the chosen action is switched off (`code: action_inactive`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "The endpoint does not exist in this workspace, or the chosen action is not one of this endpoint's actions (`code: not_found`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/requests/bulk-delete": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        }
      ],
      "post": {
        "tags": [
          "Requests"
        ],
        "operationId": "bulkDelete",
        "summary": "Bulk delete (moves them to the recycle bin)",
        "description": "Moves up to 500 captured requests to the recycle bin by id, restorable for 7 days. Ids that don't exist on the endpoint are ignored; the response says how many were actually moved.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkIds"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many were deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedCount"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/alert-channels": {
      "get": {
        "tags": [
          "Alert channels"
        ],
        "operationId": "listAlertChannels",
        "summary": "Alert channels in this workspace",
        "description": "Every configured channel with its health and subscriptions. `limit` is how many your plan allows; `integrationsEnabled` is false on plans that alert by email only, in which case any channels listed are kept but paused.",
        "responses": {
          "200": {
            "description": "The workspace's channels.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AlertChannel"
                      }
                    },
                    "totalCount": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "integrationsEnabled": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Alert channels"
        ],
        "operationId": "createAlertChannel",
        "summary": "Add an alert channel",
        "description": "Adds a channel and subscribes it to the event kinds you list. The channel is created untested: nothing counts as working until a test send has reached the provider, so follow this with `POST /{id}/test`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AlertChannelWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The channel as created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlertChannel"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/alert-channels/kinds": {
      "get": {
        "tags": [
          "Alert channels"
        ],
        "operationId": "listAlertChannelKinds",
        "summary": "Channel kinds this workspace can add",
        "description": "The destinations available to you, with the credential each one needs and any extra fields (a region, a site, a chat). Email is not listed: it is always on and is managed from your workspace contacts.",
        "responses": {
          "200": {
            "description": "Available kinds.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AlertChannelKind"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/alert-channels/events": {
      "get": {
        "tags": [
          "Alert channels"
        ],
        "operationId": "listAlertEventKinds",
        "summary": "Event kinds a channel can subscribe to",
        "description": "The alerts WebhookVault raises. Subscribe a channel to any of these.",
        "responses": {
          "200": {
            "description": "Event kinds.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AlertEventKind"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/alert-channels/deliveries": {
      "get": {
        "tags": [
          "Alert channels"
        ],
        "operationId": "listAlertDeliveries",
        "summary": "What was sent, where, and what came back",
        "description": "The workspace's alert history, newest first. Includes test sends (`isTest`) and any alert that fell back to email because a channel was failing (`emailFallback`). Retained on the same clock as your audit trail.",
        "parameters": [
          {
            "name": "channelId",
            "in": "query",
            "description": "Narrow to one channel.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "eventKind",
            "in": "query",
            "description": "Narrow to one kind of alert. Any kind a delivery can carry is accepted, including `delivery.recovered` (the Resolved notice that closes a dead-letter incident) and `action.notify` (sent by a notify action); anything else answers 400 `validation_failed`.",
            "schema": {
              "type": "string",
              "enum": [
                "watchdog.alarmed",
                "watchdog.recovered",
                "delivery.dead_letter",
                "transformation.disabled",
                "billing.past_due",
                "billing.payment_failed",
                "delivery.recovered",
                "action.notify"
              ]
            }
          },
          {
            "name": "delivered",
            "in": "query",
            "description": "true for successful sends, false for failures.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of sends.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AlertDelivery"
                      }
                    },
                    "totalCount": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "pageSize": {
                      "type": "integer"
                    },
                    "totalPages": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/alert-channels/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Alert channels"
        ],
        "operationId": "getAlertChannel",
        "summary": "One alert channel",
        "responses": {
          "200": {
            "description": "The channel.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlertChannel"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Alert channels"
        ],
        "operationId": "updateAlertChannel",
        "summary": "Change a channel's name, settings or subscriptions",
        "description": "Omit `secret` to keep the stored credential. Changing the credential or a setting returns the channel to untested, because the route it proved is no longer the route it uses. `kind` cannot change.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AlertChannelWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated channel.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlertChannel"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Alert channels"
        ],
        "operationId": "deleteAlertChannel",
        "summary": "Remove a channel",
        "description": "Alerts stop going there. The send history is kept.",
        "responses": {
          "204": {
            "description": "Removed."
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/alert-channels/{id}/test": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "tags": [
          "Alert channels"
        ],
        "operationId": "testAlertChannel",
        "summary": "Send a real test alert",
        "description": "Sends an actual message and reports what the provider said. A failure returns 200 with `delivered: false` and the provider's own words in `detail` - the send failed, the request did not.",
        "responses": {
          "200": {
            "description": "What the provider said.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlertTestResult"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/watchdogs": {
      "get": {
        "tags": [
          "Watchdogs"
        ],
        "operationId": "listWatchdogs",
        "summary": "Watchdogs in this workspace",
        "description": "Every watchdog with its current state. `pausedByPlan` marks one kept but not evaluated because the workspace is over its plan's watchdog limit, which is the difference between healthy and not being watched.",
        "responses": {
          "200": {
            "description": "The workspace's watchdogs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Watchdog"
                      }
                    },
                    "totalCount": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Watchdogs"
        ],
        "operationId": "createWatchdog",
        "summary": "Add a watchdog",
        "description": "Arms immediately, using the endpoint's existing history so there is no blind first window. GET /watchdogs/suggest proposes an interval from what the endpoint actually receives.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WatchdogWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The watchdog as created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Watchdog"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/watchdogs/suggest": {
      "get": {
        "tags": [
          "Watchdogs"
        ],
        "operationId": "suggestWatchdogCadence",
        "summary": "Propose an interval from the endpoint's own history",
        "description": "Reads what the endpoint has received and proposes an interval and grace. `available` is false when there is too little history to propose anything, and `reason` says so in words worth showing someone.",
        "parameters": [
          {
            "name": "endpointId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "match",
            "in": "query",
            "description": "Criteria to measure against, as JSON. Omit to measure every capture.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A proposed cadence.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WatchdogSuggestion"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/watchdogs/labels": {
      "get": {
        "tags": [
          "Watchdogs"
        ],
        "operationId": "listWatchdogLabels",
        "summary": "Event labels seen on an endpoint",
        "description": "What this endpoint has actually sent, with counts, for building a match.",
        "parameters": [
          {
            "name": "endpointId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Observed labels.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "label": {
                            "type": "string"
                          },
                          "count": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/watchdogs/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Watchdogs"
        ],
        "operationId": "getWatchdog",
        "summary": "One watchdog",
        "responses": {
          "200": {
            "description": "The watchdog.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Watchdog"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Watchdogs"
        ],
        "operationId": "updateWatchdog",
        "summary": "Change a watchdog",
        "description": "Changing the criteria or the cadence re-arms it against the endpoint's history.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WatchdogWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated watchdog.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Watchdog"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Watchdogs"
        ],
        "operationId": "deleteWatchdog",
        "summary": "Remove a watchdog",
        "responses": {
          "204": {
            "description": "Removed."
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/watchdogs/{id}/active": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "tags": [
          "Watchdogs"
        ],
        "operationId": "setWatchdogActive",
        "summary": "Pause or resume a watchdog",
        "description": "A paused watchdog is still fed by captures but never evaluated and never alerts, so a planned outage does not mean deleting and rebuilding it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "active": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The watchdog.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Watchdog"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/actions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        }
      ],
      "get": {
        "tags": [
          "Actions"
        ],
        "operationId": "listActions",
        "summary": "The endpoint's actions",
        "description": "Everything that happens to a capture on this endpoint, in the order it runs. Actions do NOT chain: each one sees the request exactly as it arrived, none waits for another, and one failing never stops the rest. Order is dispatch and display order, nothing more.\n\n`options` says what this workspace's plan allows, so a client can offer the same choices the dashboard does rather than discovering a refusal on save. A kind absent from `options.kinds` cannot be created.\n\nThe endpoint's `forwardingEnabled` and `forwardUrl` fields are unchanged and keep working: they are this API's view of the first forward action.",
        "responses": {
          "200": {
            "description": "The actions, the limit, and what the plan allows.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Actions"
        ],
        "operationId": "createAction",
        "summary": "Add an action to the end of the list",
        "description": "`kind` defaults to `forward`, which needs a `url`. A `notify` needs an `alertChannelId` naming one of the workspace's alert channels. A `subrequest` needs an `apiRequestId` naming one of the workspace's saved requests, plus a `slotMapping` covering every field that request declares. Saved requests and their connections are created in the dashboard Library; no API operation lists or creates them, so add a subrequest action once in the dashboard and read its `apiRequestId` back from listActions.\n\nA kind the plan does not include is refused with `code: kind_not_available`. A forward `url` is checked against the same SSRF policy the dispatcher enforces at connect time, so a private or internal address is refused here; a `subrequest` is checked the same way against its connection's base URL.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ActionWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The action as stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Action"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "409": {
            "description": "`code: limit_reached` - the plan's actions-per-endpoint limit is already used."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/actions/order": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        }
      ],
      "put": {
        "tags": [
          "Actions"
        ],
        "operationId": "reorderActions",
        "summary": "Set the order actions run in",
        "description": "`order` must list every action id on this endpoint exactly once. A partial order is refused rather than applied, because it would leave the rest at positions that no longer mean anything.\n\nReordering changes dispatch and display order only. It does not make one action wait for another, and it does not pass anything between them.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ActionOrder"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The actions in their new order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/endpoints/{endpointId}/actions/{actionId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/endpointId"
        },
        {
          "name": "actionId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          },
          "description": "The action."
        }
      ],
      "get": {
        "tags": [
          "Actions"
        ],
        "operationId": "getAction",
        "summary": "One action",
        "description": "The signing secret is never returned; `signing.signed` and `signing.setAt` are all this API says about it.",
        "responses": {
          "200": {
            "description": "The action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Action"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Actions"
        ],
        "operationId": "updateAction",
        "summary": "Change an action",
        "description": "Every field is optional and an omitted one is left as it was, so renaming an action never means restating its URL. The two exceptions are deliberate: `condition` sent as `null` clears it back to always, and `messageTemplate` sent as an empty string clears it back to a summary of the payload.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ActionWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The action as stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Action"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Actions"
        ],
        "operationId": "deleteAction",
        "summary": "Remove an action",
        "description": "The action and its transformation rules are deleted. Captures already stored are not affected, and the remaining actions close up so positions stay contiguous.",
        "responses": {
          "204": {
            "description": "Removed."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "wv_live_…",
        "description": "API key in the Authorization header: `Authorization: Bearer wv_live_…`. Keys are created on the API keys page in the app, shown once, and scoped to your workspace. `X-Api-Key: wv_live_…` is accepted as a fallback for tools that can't set Authorization."
      }
    },
    "parameters": {
      "ruleId": {
        "name": "ruleId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "A rule id from the set."
      },
      "endpointId": {
        "name": "endpointId",
        "in": "path",
        "required": true,
        "description": "The endpoint id (also the capture token in its URL).",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "requestId": {
        "name": "requestId",
        "in": "path",
        "required": true,
        "description": "The captured request id.",
        "schema": {
          "type": "integer",
          "format": "int64"
        }
      },
      "page": {
        "name": "page",
        "in": "query",
        "description": "1-based page number.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "pageSize": {
        "name": "pageSize",
        "in": "query",
        "description": "Items per page, 1–100.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 50
        }
      },
      "method": {
        "name": "method",
        "in": "query",
        "description": "Exact HTTP method, case-insensitive (e.g. POST).",
        "schema": {
          "type": "string"
        }
      },
      "state": {
        "name": "state",
        "in": "query",
        "description": "Delivery state filter.",
        "schema": {
          "type": "string",
          "enum": [
            "NotAttempted",
            "Pending",
            "Succeeded",
            "Failed",
            "Errored",
            "DeadLetter",
            "Skipped"
          ]
        }
      },
      "q": {
        "name": "q",
        "in": "query",
        "description": "Case-insensitive substring. Matches path and query string on every plan; also matches non-binary bodies on plans with full payload search.",
        "schema": {
          "type": "string"
        }
      },
      "verification": {
        "name": "verification",
        "in": "query",
        "description": "Signature verdict filter. `none` = requests that were not checked (no signing secret on the endpoint, or the plan lacks verification).",
        "schema": {
          "type": "string",
          "enum": [
            "verified",
            "failed",
            "unsigned",
            "unverifiable",
            "none"
          ]
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "No key, or an unknown/revoked key (`code: unauthorized`).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key is valid but not allowed: its plan lacks API access (`code: api_access_disabled`) or the feature (`code: feature_not_in_plan`).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "NotFound": {
        "description": "The endpoint or request does not exist in this workspace (`code: not_found`).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "ValidationFailed": {
        "description": "The request body or a query parameter is invalid (`code: validation_failed`). `detail` says exactly what to fix.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "ForwardingDisabled": {
        "description": "Invalid input (`code: validation_failed`), or forwarding is off on the endpoint (`code: forwarding_disabled`).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "RateLimited": {
        "description": "The key's per-minute budget is spent (`code: rate_limited`). Honor the `Retry-After` header.",
        "headers": {
          "Retry-After": {
            "description": "Seconds until the window resets.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details, extended with a stable machine-readable `code` and the request's `traceId` (quote it in support requests).",
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string",
            "description": "Human-readable explanation. May change wording; parse `code`, not this."
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable failure identifier, e.g. `validation_failed`."
          },
          "traceId": {
            "type": "string"
          }
        }
      },
      "TransformationRule": {
        "type": "object",
        "description": "The v1 rule document. The authoritative grammar is the JSON Schema at GET /api/v1/transformations/schema; the plain-text spec for models is at https://app.webhookvault.net/llms-transformations.txt.",
        "required": [
          "version",
          "steps"
        ],
        "properties": {
          "version": {
            "type": "integer",
            "const": 1
          },
          "name": {
            "type": "string",
            "maxLength": 120
          },
          "when": {
            "type": "object",
            "description": "all / any / not over predicates {path, op, value}. Omit to apply to every delivery."
          },
          "steps": {
            "type": "array",
            "maxItems": 64,
            "items": {
              "type": "object",
              "required": [
                "op"
              ],
              "properties": {
                "op": {
                  "type": "string",
                  "enum": [
                    "keep",
                    "remove",
                    "rename",
                    "move",
                    "set",
                    "default",
                    "redact",
                    "template",
                    "parseJson",
                    "toString",
                    "compute"
                  ],
                  "description": "compute writes a value worked out from the payload: fn names one function from a fixed table and args is a list of paths, literals and nested calls. Not an expression language. The authoritative grammar is the JSON Schema at GET /api/v1/transformations/schema."
                },
                "enabled": {
                  "type": "boolean",
                  "default": true
                }
              },
              "additionalProperties": true
            }
          }
        },
        "example": {
          "version": 1,
          "name": "strip PII, flatten invoice",
          "when": {
            "all": [
              {
                "path": "body.type",
                "op": "startsWith",
                "value": "invoice."
              }
            ]
          },
          "steps": [
            {
              "op": "keep",
              "paths": [
                "body.id",
                "body.type",
                "body.data.object"
              ]
            },
            {
              "op": "rename",
              "from": "body.data.object.id",
              "to": "body.invoiceId"
            },
            {
              "op": "redact",
              "paths": [
                "body.data.object.customer_email"
              ],
              "with": "***"
            },
            {
              "op": "set",
              "path": "headers.X-Relayed-By",
              "value": "webhookvault"
            },
            {
              "op": "template",
              "path": "body.summary",
              "template": "{{body.type}} {{body.invoiceId}}"
            }
          ]
        }
      },
      "TransformationRuleView": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "position": {
            "type": "integer",
            "description": "0-based order in the set; the dispatcher runs position 0 first."
          },
          "name": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "disabledReason": {
            "type": "string",
            "nullable": true,
            "description": "Why WebhookVault switched the rule off, or null. Set when a rule faults on enough consecutive deliveries to be taken out of the delivery path; null when it is running or when a person disabled it. Editing the rule, restoring a version or re-enabling it clears this."
          },
          "condition": {
            "type": "string",
            "description": "The `when` clause in words (\"every delivery\" when there is none)."
          },
          "rule": {
            "$ref": "#/components/schemas/TransformationRule"
          },
          "version": {
            "type": "integer",
            "description": "Increments on every changed save or restore; delivery attempts record it."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TransformationSet": {
        "type": "object",
        "properties": {
          "endpointId": {
            "type": "string",
            "format": "uuid"
          },
          "destinationId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The forward destination the set is attached to; null when the endpoint has none."
          },
          "destinationUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TransformationRuleView"
            },
            "description": "In run order."
          },
          "maxRules": {
            "type": "integer"
          },
          "featureInPlan": {
            "type": "boolean",
            "description": "False on plans without transformations: stored rules are kept but deliveries pass through with a note."
          },
          "schemaUrl": {
            "type": "string"
          }
        }
      },
      "TransformationUpdate": {
        "type": "object",
        "description": "At least one of the two.",
        "properties": {
          "rule": {
            "$ref": "#/components/schemas/TransformationRule"
          },
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "TransformationVersion": {
        "type": "object",
        "properties": {
          "version": {
            "type": "integer"
          },
          "rule": {
            "$ref": "#/components/schemas/TransformationRule"
          },
          "savedBy": {
            "type": "string",
            "description": "A user id, `api-key`, or `system:migration`."
          },
          "savedAt": {
            "type": "string",
            "format": "date-time"
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "description": "`created`, `saved`, `restored from v3`, or the migration note."
          },
          "current": {
            "type": "boolean"
          }
        }
      },
      "RecycleBinItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The captured request's id. Restore it with this."
          },
          "endpointId": {
            "type": "string",
            "format": "uuid"
          },
          "endpointName": {
            "type": "string"
          },
          "receivedAt": {
            "type": "string",
            "format": "date-time"
          },
          "deletedAt": {
            "type": "string",
            "format": "date-time"
          },
          "deletedBy": {
            "type": "string",
            "description": "The display name of the person who deleted it, `API key` when a key did, or `system`."
          },
          "deleteReason": {
            "type": "string",
            "enum": [
              "deleted",
              "bulk",
              "clear"
            ]
          },
          "purgesAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the retention sweep removes it for good: the earlier of the bin's 7-day window and the plan's retention window."
          },
          "method": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "contentLength": {
            "type": "integer",
            "format": "int64"
          }
        }
      },
      "Delivery": {
        "type": "object",
        "description": "Latest delivery outcome for the request. `state` NotAttempted means the endpoint had no active action when the request arrived; `Skipped` means every action carried a condition and none matched this request, so nothing was queued.",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "NotAttempted",
              "Pending",
              "Succeeded",
              "Failed",
              "Errored",
              "DeadLetter",
              "Skipped"
            ]
          },
          "statusCode": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          },
          "at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "durationMs": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "DeliveryDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Delivery"
          },
          {
            "type": "object",
            "properties": {
              "endpointId": {
                "type": "string",
                "format": "uuid"
              },
              "transformNote": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Per rule and version, what it did; rules separated by ` || `."
              },
              "sent": {
                "type": "object",
                "description": "The request as it left the vault, after transformation.",
                "properties": {
                  "method": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "headers": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "body": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Text, or base64 with `bodyIsBinary`."
                  },
                  "bodyIsBinary": {
                    "type": "boolean"
                  },
                  "bodyTruncated": {
                    "type": "boolean"
                  }
                }
              },
              "received": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "Null when no response came back (unreachable, timed out, interrupted, still sending).",
                "properties": {
                  "statusCode": {
                    "type": "integer"
                  },
                  "headers": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "body": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Text, or `base64:` prefixed when not valid UTF-8."
                  },
                  "bodyTruncated": {
                    "type": "boolean"
                  },
                  "contentType": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        ]
      },
      "TransformationView": {
        "type": "object",
        "description": "The compatibility view of the singular route: the first rule of the set.",
        "properties": {
          "ruleId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "ruleCount": {
            "type": "integer"
          },
          "endpointId": {
            "type": "string",
            "format": "uuid"
          },
          "destinationId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The forward destination the rule is attached to; null when the endpoint has none."
          },
          "destinationUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "rule": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/TransformationRule"
              },
              {
                "type": "null"
              }
            ]
          },
          "version": {
            "type": "integer",
            "description": "Increments on every save or removal; delivery attempts record the version they used."
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "featureInPlan": {
            "type": "boolean",
            "description": "False on plans without transformations: a stored rule is kept but deliveries pass through with a note."
          },
          "schemaUrl": {
            "type": "string"
          }
        }
      },
      "TransformationPreviewRequest": {
        "type": "object",
        "required": [
          "rule"
        ],
        "properties": {
          "rule": {
            "$ref": "#/components/schemas/TransformationRule"
          },
          "sampleRequestId": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "A saved request in your workspace."
          },
          "sampleRequest": {
            "type": [
              "object",
              "null"
            ],
            "description": "Inline sample when no saved request fits.",
            "properties": {
              "method": {
                "type": "string",
                "default": "POST"
              },
              "path": {
                "type": "string"
              },
              "queryString": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "headers": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "body": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The body as text (JSON text for JSON bodies)."
              },
              "contentType": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "TransformationPreview": {
        "type": "object",
        "properties": {
          "matched": {
            "type": "boolean",
            "description": "False when `when` did not match: the delivery goes out unchanged."
          },
          "sampleRequestId": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "output": {
            "type": "object",
            "properties": {
              "headers": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "body": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "contentType": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "queryString": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer"
                },
                "op": {
                  "type": "string"
                },
                "applied": {
                  "type": "boolean"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "note": {
            "type": "string"
          }
        }
      },
      "Me": {
        "type": "object",
        "properties": {
          "key": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "prefix": {
                "type": "string",
                "description": "Identification prefix, e.g. wv_live_3fk9Qm2x. Never the full key."
              },
              "name": {
                "type": "string"
              }
            }
          },
          "workspace": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "plan": {
            "type": "object",
            "properties": {
              "key": {
                "type": "string"
              },
              "displayName": {
                "type": "string"
              }
            }
          },
          "limits": {
            "type": "object",
            "properties": {
              "endpointsMax": {
                "type": "integer",
                "format": "int64"
              },
              "endpointsUsed": {
                "type": "integer"
              },
              "storedRequestsPerEndpoint": {
                "type": "integer",
                "format": "int64"
              },
              "retentionDays": {
                "type": "integer",
                "format": "int64"
              },
              "payloadMaxBytes": {
                "type": "integer",
                "format": "int64"
              },
              "captureRatePerMinute": {
                "type": "integer",
                "format": "int64"
              },
              "apiRatePerMinute": {
                "type": "integer",
                "format": "int64"
              }
            }
          }
        }
      },
      "Endpoint": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": "string",
            "description": "The capture URL. Send webhooks here."
          },
          "isActive": {
            "type": "boolean"
          },
          "suspendedByPlan": {
            "type": "boolean",
            "description": "True when the endpoint is off because the plan has room for fewer active endpoints than this workspace holds, rather than because you switched it off. Nothing was deleted; switch another endpoint off to make room, or upgrade, and it comes back exactly as it was."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastRequestAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Ephemeral endpoints only: when this endpoint stops answering and is swept away."
          },
          "storedRequestCount": {
            "type": "integer",
            "format": "int64"
          },
          "responseStatusCode": {
            "type": "integer"
          },
          "responseContentType": {
            "type": "string"
          },
          "responseHeaders": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "string"
            }
          },
          "responseBody": {
            "type": [
              "string",
              "null"
            ]
          },
          "provider": {
            "type": [
              "string",
              "null"
            ],
            "description": "Provider slug from GET /api/v1/providers; null for a generic endpoint."
          },
          "forwardingEnabled": {
            "type": "boolean",
            "deprecated": true,
            "description": "Deprecated: read the endpoint's actions instead (GET /api/v1/endpoints/{id}/actions). True when at least one action is switched on. Kept, and still filled in, for integrations written against the single-URL contract."
          },
          "forwardUrl": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "Deprecated: read the endpoint's actions instead (GET /api/v1/endpoints/{id}/actions). The URL of the endpoint's first forward action (by position), null when the endpoint has no forward action. Kept, and still filled in, for integrations written against the single-URL contract."
          },
          "forwardSigning": {
            "type": "object",
            "deprecated": true,
            "description": "Deprecated: each action reports its own signing (GET /api/v1/endpoints/{id}/actions). Whether deliveries from the first forward action are signed. Never the secret itself.",
            "properties": {
              "signed": {
                "type": "boolean"
              },
              "setAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "When the signing secret was last set, or null when deliveries are unsigned."
              }
            }
          },
          "verification": {
            "$ref": "#/components/schemas/EndpointVerification"
          }
        }
      },
      "EndpointVerification": {
        "type": "object",
        "description": "How the endpoint verifies signatures. The secret itself is never returned.",
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "preset",
              "manual",
              "unsupported",
              "none"
            ],
            "description": "preset = the provider's scheme; manual = the endpoint's own raw-body HMAC description; unsupported = the provider signs with a scheme the vault does not verify yet; none = nothing configured."
          },
          "scheme": {
            "type": [
              "string",
              "null"
            ],
            "description": "Human description of the scheme in force."
          },
          "family": {
            "type": [
              "string",
              "null"
            ]
          },
          "secretLabel": {
            "type": "string",
            "description": "What the provider calls the value to paste as signingSecret."
          },
          "secretSet": {
            "type": "boolean"
          },
          "secretSetAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "handshakeTokenSet": {
            "type": "boolean"
          },
          "handshakeTokenNeeded": {
            "type": "boolean",
            "description": "True for providers whose subscribe handshake carries a verify token (Meta, Strava)."
          },
          "manual": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "header": {
                "type": "string"
              },
              "algorithm": {
                "type": "string",
                "enum": [
                  "sha1",
                  "sha256",
                  "sha512"
                ]
              },
              "encoding": {
                "type": "string",
                "enum": [
                  "hex",
                  "base64"
                ]
              },
              "prefix": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "CreateEndpoint": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "responseStatusCode": {
            "type": "integer",
            "minimum": 100,
            "maximum": 599,
            "default": 200,
            "description": "What the capture URL answers senders. 3xx statuses are refused: a capture response never redirects."
          },
          "responseContentType": {
            "type": "string",
            "default": "application/json",
            "description": "One media type from the capture response allowlist: application/json (or any +json type), application/problem+json, text/plain, text/csv, application/xml, text/xml, application/soap+xml, application/x-www-form-urlencoded or application/octet-stream. HTML, images and scripts are refused; captures are replies to senders, never pages."
          },
          "responseHeaders": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "string"
            }
          },
          "responseBody": {
            "type": [
              "string",
              "null"
            ]
          },
          "forwardingEnabled": {
            "type": "boolean",
            "default": false,
            "deprecated": true,
            "description": "Deprecated: create the endpoint, then add a forward action (POST /api/v1/endpoints/{id}/actions). Still honoured: with forwardUrl it creates the endpoint's first forward action."
          },
          "forwardUrl": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "Deprecated: add a forward action instead (POST /api/v1/endpoints/{id}/actions). Still honoured: it becomes the endpoint's first forward action. Public http(s) URL to relay captures to. Private and internal addresses are rejected."
          },
          "provider": {
            "type": [
              "string",
              "null"
            ],
            "description": "Provider slug from GET /api/v1/providers (e.g. \"stripe\"). Unknown slugs are rejected; omit for a generic endpoint."
          },
          "ttlSeconds": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 60,
            "maximum": 86400,
            "description": "Makes the endpoint ephemeral (CI mode): expires and is deleted after this many seconds. Omit for permanent."
          },
          "signingSecret": {
            "type": [
              "string",
              "null"
            ],
            "description": "The provider's signing secret (or public key for SendGrid). Write-only: stored protected, never returned. Verdicts need a plan with signature verification."
          },
          "forwardSigningSecret": {
            "type": "string",
            "nullable": true,
            "deprecated": true,
            "description": "Deprecated: add a forward action with `signingSecret` instead (POST /api/v1/endpoints/{id}/actions). Still honoured, and applied to the endpoint's first forward action. Secret used to SIGN forwards to `forwardUrl`, so the receiver can verify the delivery came from WebhookVault. Write-only: stored protected and never returned. At least 16 characters. Same convention as `forwardUrl` on an update: omitted leaves it unchanged, an empty string stops signing. Not to be confused with `signingSecret`, which VERIFIES what arrives."
          },
          "handshakeToken": {
            "type": [
              "string",
              "null"
            ],
            "description": "Verify token for Meta/Strava subscribe handshakes."
          },
          "signatureHeader": {
            "type": [
              "string",
              "null"
            ],
            "description": "Manual scheme for generic endpoints: the header carrying the HMAC of the raw body."
          },
          "signatureAlgorithm": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "sha1",
              "sha256",
              "sha512",
              "rsa-sha256",
              "rsa-sha512",
              "ed25519",
              null
            ],
            "description": "Manual scheme digest. The rsa- and ed25519 entries change what `signingSecret` holds: a PUBLIC key the provider publishes rather than a shared secret."
          },
          "signatureEncoding": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "hex",
              "base64",
              null
            ]
          },
          "signaturePrefix": {
            "type": [
              "string",
              "null"
            ],
            "description": "Literal prefix before the digest, e.g. \"sha256=\"."
          }
        }
      },
      "UpdateEndpoint": {
        "type": "object",
        "description": "All fields optional; omitted = unchanged. `forwardUrl`: empty string clears it. `responseHeaders`: `{}` clears them. `provider`: empty string clears it.",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "responseStatusCode": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 100,
            "maximum": 599,
            "description": "3xx statuses are refused: a capture response never redirects."
          },
          "responseContentType": {
            "type": [
              "string",
              "null"
            ],
            "description": "One media type from the capture response allowlist (JSON and +json types, problem+json, text/plain, text/csv, application/xml, text/xml, application/soap+xml, form-encoded, octet-stream). HTML, images and scripts are refused. Response headers may not set Content-Type, cookies, redirects, security policies or transport headers."
          },
          "responseHeaders": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "string"
            }
          },
          "responseBody": {
            "type": [
              "string",
              "null"
            ]
          },
          "isActive": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "false turns the endpoint off: senders get 410."
          },
          "forwardingEnabled": {
            "type": [
              "boolean",
              "null"
            ],
            "deprecated": true,
            "description": "Deprecated: switch an action on or off instead (PUT /api/v1/endpoints/{id}/actions/{actionId} with isActive). Still honoured: it switches the endpoint's first forward action, never a notify or a subrequest. When the endpoint has no forward action and no `forwardUrl` is sent, `true` is refused (400) because there is no forward to switch on, and `false` is accepted and changes nothing, because nothing forwards already."
          },
          "forwardUrl": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "Deprecated: edit the forward action instead (PUT /api/v1/endpoints/{id}/actions/{actionId}). Still honoured: it changes the endpoint's first forward action (adding one when there is none), an empty string removes that forward action, and omitted leaves it unchanged. A notify or a subrequest is never changed or removed through this field."
          },
          "provider": {
            "type": [
              "string",
              "null"
            ],
            "description": "Provider slug; empty string clears it, omitted leaves it unchanged."
          },
          "signingSecret": {
            "type": [
              "string",
              "null"
            ],
            "description": "Write-only. Omitted = unchanged, empty string = cleared (stored verdicts are removed), value = replaced (history re-verified in the background)."
          },
          "forwardSigningSecret": {
            "type": "string",
            "nullable": true,
            "deprecated": true,
            "description": "Deprecated: set `signingSecret` on the forward action instead (PUT /api/v1/endpoints/{id}/actions/{actionId}). Still honoured, and applied to the endpoint's first forward action; refused when the endpoint has no forward action and no `forwardUrl` is sent. Sent on its own, it does not re-check the forward's URL, so a rotation never depends on the receiver's DNS. Secret used to SIGN forwards to `forwardUrl`, so the receiver can verify the delivery came from WebhookVault. Write-only: stored protected and never returned. At least 16 characters. Same convention as `forwardUrl` on an update: omitted leaves it unchanged, an empty string stops signing. Not to be confused with `signingSecret`, which VERIFIES what arrives."
          },
          "handshakeToken": {
            "type": [
              "string",
              "null"
            ],
            "description": "Omitted = unchanged, empty string = cleared."
          },
          "signatureHeader": {
            "type": [
              "string",
              "null"
            ],
            "description": "Manual scheme header; empty string removes the manual scheme."
          },
          "signatureAlgorithm": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "sha1",
              "sha256",
              "sha512",
              "rsa-sha256",
              "rsa-sha512",
              "ed25519",
              null
            ],
            "description": "Manual scheme digest. The rsa- and ed25519 entries change what `signingSecret` holds: a PUBLIC key the provider publishes rather than a shared secret."
          },
          "signatureEncoding": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "hex",
              "base64",
              null
            ]
          },
          "signaturePrefix": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Provider": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string",
            "description": "The value an endpoint's `provider` field takes."
          },
          "name": {
            "type": "string"
          },
          "eventSource": {
            "type": "string",
            "enum": [
              "none",
              "header",
              "bodyPath"
            ],
            "description": "Where the provider announces its event type on each delivery."
          },
          "eventKey": {
            "type": [
              "string",
              "null"
            ],
            "description": "Header name (eventSource=header) or dotted body path (eventSource=bodyPath)."
          },
          "signatureHeader": {
            "type": [
              "string",
              "null"
            ],
            "description": "The header carrying the provider's signature, when it signs deliveries."
          },
          "challengeResponse": {
            "type": "boolean",
            "description": "True when the provider demands a subscription handshake before sending."
          },
          "docsUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "setupPath": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where the webhook URL is entered in the provider's own console, as a path of section names joined by \" > \" (for example \"Developers > Webhooks > Add endpoint\"). For the few providers with no console field it is the one action that registers the URL instead (Telegram: \"Call the Bot API setWebhook method with this URL\"). Null when not documented."
          },
          "tier": {
            "type": "integer",
            "description": "1 = full event-source and signature metadata; 2 = named and selectable."
          },
          "verification": {
            "type": [
              "object",
              "null"
            ],
            "description": "The provider's signature scheme as the vault knows it; null when the provider does not sign.",
            "properties": {
              "supported": {
                "type": "boolean",
                "description": "False when the scheme is known but not verified yet (requests get an unverifiable verdict)."
              },
              "scheme": {
                "type": "string"
              },
              "secretLabel": {
                "type": "string"
              }
            }
          },
          "handshakeTokenNeeded": {
            "type": "boolean"
          }
        }
      },
      "ProviderList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Provider"
            }
          }
        }
      },
      "EndpointStats": {
        "type": "object",
        "properties": {
          "endpointId": {
            "type": "string",
            "format": "uuid"
          },
          "storedRequestCount": {
            "type": "integer",
            "format": "int64"
          },
          "lastRequestAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "isActive": {
            "type": "boolean"
          },
          "suspendedByPlan": {
            "type": "boolean",
            "description": "True when the plan switched this endpoint off, not the customer."
          },
          "retentionDays": {
            "type": "integer",
            "format": "int64"
          },
          "forwardingEnabled": {
            "type": "boolean"
          },
          "pendingJobs": {
            "type": "integer",
            "description": "Deliveries queued or in flight."
          },
          "deadLetteredJobs": {
            "type": "integer",
            "description": "Deliveries that exhausted retries. Replay restarts them."
          }
        }
      },
      "CapturedRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "endpointId": {
            "type": "string",
            "format": "uuid"
          },
          "receivedAt": {
            "type": "string",
            "format": "date-time"
          },
          "method": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "queryString": {
            "type": [
              "string",
              "null"
            ]
          },
          "headers": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "string"
            }
          },
          "body": {
            "type": [
              "string",
              "null"
            ],
            "description": "The payload. Base64 of the original bytes when `bodyIsBinary` is true; a placeholder when `bodyTruncated` is true."
          },
          "bodyIsBinary": {
            "type": "boolean"
          },
          "bodyTruncated": {
            "type": "boolean",
            "description": "The body exceeded the plan's payload cap and only its size was recorded."
          },
          "contentType": {
            "type": [
              "string",
              "null"
            ]
          },
          "sourceIp": {
            "type": [
              "string",
              "null"
            ]
          },
          "contentLength": {
            "type": "integer",
            "format": "int64"
          },
          "verification": {
            "$ref": "#/components/schemas/Verification"
          },
          "delivery": {
            "$ref": "#/components/schemas/Delivery"
          }
        }
      },
      "Verification": {
        "type": "object",
        "description": "Signature verdict computed at capture time from the raw bytes (re-computed when the endpoint's secret or scheme changes).",
        "properties": {
          "verdict": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "verified",
              "failed",
              "unsigned",
              "unverifiable",
              null
            ],
            "description": "null = not checked: no signing secret on the endpoint, or the plan lacks verification."
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why: the header that matched or did not, the handshake answered, or the scheme not supported. Never the expected signature."
          }
        }
      },
      "DeliveryAttempt": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "attemptNumber": {
            "type": "integer"
          },
          "url": {
            "type": "string"
          },
          "startedAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "state": {
            "type": "string",
            "description": "Succeeded, Failed, Errored, DeadLetter (retry budget exhausted), Pending (in flight), or Interrupted (unfinished past the visibility window: the worker died mid-send)."
          },
          "statusCode": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "durationMs": {
            "type": [
              "integer",
              "null"
            ]
          },
          "transformed": {
            "type": "boolean",
            "description": "A transformation rule changed this delivery."
          },
          "transformationVersion": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Version of the single rule that ran and matched; null with several rules, no rule, or a rule that was skipped, unmatched or invalid."
          },
          "transformNote": {
            "type": [
              "string",
              "null"
            ],
            "description": "Per rule and version, what it did step by step, or why it was skipped (plan, invalid rule, engine failure)."
          },
          "hasDetail": {
            "type": "boolean",
            "description": "The full sent/received record exists: read it at GET …/deliveries/{id}."
          }
        }
      },
      "ReplayOutcome": {
        "type": "object",
        "properties": {
          "state": {
            "type": "string"
          },
          "statusCode": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          },
          "durationMs": {
            "type": "integer"
          },
          "delivered": {
            "type": "boolean"
          }
        }
      },
      "BulkIds": {
        "type": "object",
        "required": [
          "ids"
        ],
        "properties": {
          "ids": {
            "type": "array",
            "items": {
              "type": "integer",
              "format": "int64"
            },
            "minItems": 1,
            "maxItems": 500
          }
        }
      },
      "BulkReplay": {
        "type": "object",
        "required": [
          "ids"
        ],
        "properties": {
          "ids": {
            "type": "array",
            "items": {
              "type": "integer",
              "format": "int64"
            },
            "minItems": 1,
            "maxItems": 500
          },
          "actionId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Optional. The one action of this endpoint to replay the requests through. Omit it (or send null) to replay to every action that is switched on."
          }
        }
      },
      "BulkReplayOutcome": {
        "type": "object",
        "properties": {
          "requestsEnqueued": {
            "type": "integer"
          },
          "jobsCreated": {
            "type": "integer"
          },
          "skippedTruncated": {
            "type": "integer",
            "description": "Requests whose stored body was an oversize placeholder, so not replayable."
          },
          "skippedMissing": {
            "type": "integer",
            "description": "Ids that don't exist on this endpoint."
          }
        }
      },
      "DeletedCount": {
        "type": "object",
        "properties": {
          "deletedCount": {
            "type": "integer"
          }
        }
      },
      "EndpointPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Endpoint"
            }
          },
          "totalCount": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "pageSize": {
            "type": "integer"
          },
          "totalPages": {
            "type": "integer"
          }
        }
      },
      "RequestPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CapturedRequest"
            }
          },
          "totalCount": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "pageSize": {
            "type": "integer"
          },
          "totalPages": {
            "type": "integer"
          }
        }
      },
      "AlertChannel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "description": "Which provider. See GET /alert-channels/kinds."
          },
          "name": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean",
            "description": "False = kept but silent."
          },
          "health": {
            "type": "string",
            "enum": [
              "untested",
              "verified",
              "failing"
            ],
            "description": "untested until a send succeeds; failing after three consecutive failures, at which point alerts also go to email."
          },
          "credentialSet": {
            "type": "boolean"
          },
          "credentialSetAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "connected": {
            "type": "boolean",
            "description": "True when set up through the provider's own connect flow rather than a pasted credential."
          },
          "destination": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where it posts, in the provider's words, when known."
          },
          "mutedUntil": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AlertSubscription"
            }
          },
          "lastSuccessAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastFailureAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastError": {
            "type": [
              "string",
              "null"
            ]
          },
          "consecutiveFailures": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AlertSubscription": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string"
          },
          "minSeverity": {
            "type": "string",
            "enum": [
              "info",
              "warning",
              "critical"
            ],
            "description": "Alerts below this are skipped."
          }
        }
      },
      "AlertChannelWrite": {
        "type": "object",
        "required": [
          "name",
          "events"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "description": "Required on create; ignored on update."
          },
          "name": {
            "type": "string",
            "maxLength": 120
          },
          "secret": {
            "type": [
              "string",
              "null"
            ],
            "description": "The credential. Write-only and never returned. Omit on update to keep the stored one."
          },
          "config": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "string"
            },
            "description": "Per-kind settings such as a region or a chat id. See GET /alert-channels/kinds."
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Event kinds to receive. At least one."
          },
          "enabled": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "AlertChannelKind": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "supportsResolve": {
            "type": "boolean",
            "description": "True when an alarm and its recovery are one incident on this provider."
          },
          "fields": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "required": {
                  "type": "boolean"
                },
                "options": {
                  "type": [
                    "array",
                    "null"
                  ],
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "credentialLabel": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the credential is called, so a form can label its field. Null when the kind needs none. This is the label, never a credential."
          }
        }
      },
      "AlertEventKind": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string"
          },
          "description": {
            "type": "string"
          }
        }
      },
      "AlertDelivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "channelId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Null for email, which has no channel record, or for a channel since removed."
          },
          "channelName": {
            "type": "string"
          },
          "channelKind": {
            "type": "string"
          },
          "eventKind": {
            "type": "string"
          },
          "severity": {
            "type": "string"
          },
          "sentAt": {
            "type": "string",
            "format": "date-time"
          },
          "delivered": {
            "type": "boolean"
          },
          "attempts": {
            "type": "integer"
          },
          "statusCode": {
            "type": [
              "integer",
              "null"
            ]
          },
          "detail": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the provider said, when it failed."
          },
          "isTest": {
            "type": "boolean"
          },
          "emailFallback": {
            "type": "boolean",
            "description": "True when this went by email because a channel was failing."
          },
          "dedupKey": {
            "type": [
              "string",
              "null"
            ],
            "description": "Ties an alarm to its recovery."
          },
          "subjectId": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "AlertTestResult": {
        "type": "object",
        "properties": {
          "delivered": {
            "type": "boolean"
          },
          "statusCode": {
            "type": [
              "integer",
              "null"
            ]
          },
          "detail": {
            "type": [
              "string",
              "null"
            ]
          },
          "health": {
            "type": "string",
            "enum": [
              "untested",
              "verified",
              "failing"
            ]
          }
        }
      },
      "Watchdog": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "endpointId": {
            "type": "string",
            "format": "uuid"
          },
          "endpointName": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "enum": [
              "waiting",
              "ok",
              "overdue",
              "alarmed",
              "recovered",
              "paused",
              "plan"
            ],
            "description": "waiting = armed, nothing seen yet. overdue = past the interval, inside grace, silent. alarmed = past grace, alert sent. paused = switched off. plan = over the plan's limit and not evaluated."
          },
          "active": {
            "type": "boolean"
          },
          "pausedByPlan": {
            "type": "boolean"
          },
          "match": {
            "type": [
              "object",
              "null"
            ],
            "description": "Criteria a capture must satisfy to count. Null = every capture counts."
          },
          "expectedIntervalSeconds": {
            "type": "integer"
          },
          "graceSeconds": {
            "type": "integer"
          },
          "armedAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastMatchedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastMatchedRequestId": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "dueAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the next matching capture is expected by."
          },
          "alarmAt": {
            "type": "string",
            "format": "date-time",
            "description": "Due plus grace. Silence past this raises the alarm."
          },
          "alarmedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "recoveredAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastAlertedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "alertContactIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WatchdogWrite": {
        "type": "object",
        "required": [
          "endpointId",
          "name",
          "expectedIntervalSeconds"
        ],
        "properties": {
          "endpointId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "maxLength": 120
          },
          "match": {
            "type": [
              "object",
              "null"
            ],
            "description": "Criteria in the transformation grammar. Omit for every capture."
          },
          "expectedIntervalSeconds": {
            "type": "integer",
            "description": "One matching capture per this many seconds."
          },
          "graceSeconds": {
            "type": "integer",
            "description": "Jitter allowance after the interval before alarming."
          },
          "alertContactIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Technical contacts to alert on top of the workspace's base contacts."
          }
        }
      },
      "WatchdogSuggestion": {
        "type": "object",
        "properties": {
          "available": {
            "type": "boolean"
          },
          "reason": {
            "type": "string"
          },
          "samples": {
            "type": "integer"
          },
          "expectedIntervalSeconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "graceSeconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "observedP90Seconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "newestAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "oldestAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ActionWrite": {
        "type": "object",
        "description": "Fields to set on an action. On update, an omitted field is left unchanged.",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "forward",
              "notify",
              "subrequest"
            ],
            "description": "What the action does. Defaults to `forward` on create. `forward` POSTs the capture to `url`; `notify` sends a message through an alert channel; `subrequest` sends one of the workspace's saved requests, over its connection, with this endpoint's capture filling the request's fields."
          },
          "name": {
            "type": "string",
            "maxLength": 200,
            "description": "What you call this action. How it is told apart from the others in the dashboard and in delivery records."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "Absolute http or https URL for a `forward`. Private and internal addresses are refused. Ignored by a `notify`."
          },
          "isActive": {
            "type": "boolean",
            "description": "False pauses the action without deleting it or its transformation rules."
          },
          "condition": {
            "type": "object",
            "nullable": true,
            "description": "Whether this action runs at all, in the same predicate grammar a transformation's `when` uses. Omit to leave unchanged; send `null` to clear it back to always. This decides whether a delivery HAPPENS, which a transformation's `when` cannot: that only decides whether a payload is shaped."
          },
          "alertChannelId": {
            "type": "string",
            "format": "uuid",
            "description": "The workspace alert channel a `notify` sends through. Required for a `notify`, ignored by a `forward`. List them with `GET /api/v1/alert-channels`."
          },
          "messageTemplate": {
            "type": "string",
            "maxLength": 1000,
            "description": "The message a `notify` sends, with placeholders resolved against the delivery. A placeholder is a field path in double braces, using the same roots a condition does (`body`, `headers`, `query`, `path`) - for example `New order: {{body.order.reference}} for {{body.customer.name}}`. A field the payload does not carry renders empty rather than failing the delivery. Send an empty string to clear it back to a summary of the payload."
          },
          "apiRequestId": {
            "type": "string",
            "format": "uuid",
            "description": "The saved request a `subrequest` sends. Required for a `call`, ignored by the other kinds."
          },
          "slotMapping": {
            "type": "object",
            "nullable": true,
            "additionalProperties": {
              "type": "string"
            },
            "description": "Which of this endpoint's fields fill the request's fields, as an object of `field name` to path: `{\"subject\": \"body.order.reference\"}`. Paths use the same roots a condition does. EVERY field the request declares must be mapped or the action is refused - a half-mapped call would post a malformed request to a real API and nothing would show that it went wrong. Omit to leave unchanged; send `null` to clear."
          },
          "signingSecret": {
            "type": "string",
            "nullable": true,
            "description": "Secret a `forward` signs its deliveries with (the `wv-signature` header), so the receiver can verify they came from WebhookVault. Write-only: stored protected and never returned; the action reports only whether it is signed and since when. At least 16 characters. Omit to leave unchanged, send an empty string to stop signing, send a value to set or rotate it. Refused on a `notify` or a `subrequest`. This replaces the endpoint's deprecated `forwardSigningSecret`."
          }
        }
      },
      "Action": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "forward",
              "notify",
              "subrequest"
            ]
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "position": {
            "type": "integer",
            "description": "Zero-based dispatch and display order."
          },
          "url": {
            "type": "string",
            "description": "The forward target. Empty for a `notify`, which has nowhere to POST."
          },
          "isActive": {
            "type": "boolean"
          },
          "suspendedByPlan": {
            "type": "boolean",
            "description": "True when the action is paused because the plan has room for fewer actions on an endpoint, or no longer includes this kind, rather than because you paused it. Nothing was deleted; it returns on upgrade exactly as it was."
          },
          "condition": {
            "type": "object",
            "nullable": true,
            "description": "Null means the action always runs."
          },
          "conditionDescription": {
            "type": "string",
            "description": "The condition in words, as the dashboard shows it."
          },
          "signing": {
            "type": "object",
            "description": "Whether forwards from this action are signed, and since when. Never the secret itself.",
            "properties": {
              "signed": {
                "type": "boolean"
              },
              "setAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          },
          "ruleCount": {
            "type": "integer",
            "description": "Enabled transformation rules on this action."
          },
          "alertChannelId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "alertChannelName": {
            "type": "string",
            "nullable": true
          },
          "messageTemplate": {
            "type": "string",
            "nullable": true,
            "description": "Null means the notification carries a summary of the payload instead."
          },
          "channelUnavailable": {
            "type": "boolean",
            "description": "True when a `notify` names a channel that was deleted or switched off. The action is kept rather than removed, and needs attention before it can send."
          },
          "apiRequestId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "apiRequestName": {
            "type": "string",
            "nullable": true
          },
          "slotMapping": {
            "type": "object",
            "nullable": true,
            "additionalProperties": {
              "type": "string"
            },
            "description": "Which of this endpoint's fields fill the request's fields."
          },
          "requestUnavailable": {
            "type": "boolean",
            "description": "True when a `subrequest` names a saved request that was deleted or switched off. The action is kept rather than removed, and needs attention before it can send."
          }
        }
      },
      "ActionList": {
        "type": "object",
        "properties": {
          "actions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Action"
            }
          },
          "limit": {
            "type": "integer",
            "format": "int64",
            "description": "Actions per endpoint on this plan."
          },
          "canAddMore": {
            "type": "boolean"
          },
          "options": {
            "$ref": "#/components/schemas/ActionOptions"
          }
        }
      },
      "ActionOptions": {
        "type": "object",
        "description": "What this workspace's plan lets it choose. A kind absent here cannot be created, so a client should not offer it.",
        "properties": {
          "kinds": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "forward",
                "notify",
                "subrequest"
              ]
            },
            "description": "The kinds that may be created. `notify` is absent entirely when the plan does not include alert channels."
          },
          "channels": {
            "type": "array",
            "description": "The alert channels a `notify` could send through. Never carries a credential.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "kind": {
                  "type": "string"
                },
                "isEnabled": {
                  "type": "boolean"
                },
                "isConfigured": {
                  "type": "boolean",
                  "description": "Whether a credential has been set. Never the credential."
                }
              }
            }
          },
          "canCreateChannel": {
            "type": "boolean",
            "description": "False when the plan has no alert channels, and false when the workspace has used its allowance."
          },
          "planName": {
            "type": "string"
          }
        }
      },
      "ActionOrder": {
        "type": "object",
        "required": [
          "order"
        ],
        "properties": {
          "order": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Every action id on this endpoint, exactly once, in the order you want them."
          }
        }
      }
    }
  }
}
