{
  "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://webhookvault.dev", "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." }
  ],
  "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 — 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" },
          "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}/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/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",
        "description": "Deletes every stored request on the endpoint. 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/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",
        "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}/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 to the endpoint's forward destination and waits for the outcome. The attempt is recorded in the delivery history like any other. Requires forwarding to be enabled on the endpoint.",
        "responses": {
          "200": {
            "description": "The delivery outcome of this replay.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReplayOutcome" } } }
          },
          "400": { "$ref": "#/components/responses/ForwardingDisabled" },
          "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}/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`) — replaying a placeholder would lie to the target.\n\nPlan-gated: requires the bulk replay feature.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BulkIds" } } }
        },
        "responses": {
          "200": {
            "description": "What was queued and what was skipped.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BulkReplayOutcome" } } }
          },
          "400": { "$ref": "#/components/responses/ForwardingDisabled" },
          "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-delete": {
      "parameters": [{ "$ref": "#/components/parameters/endpointId" }],
      "post": {
        "tags": ["Requests"],
        "operationId": "bulkDelete",
        "summary": "Bulk delete",
        "description": "Deletes up to 500 captured requests by id. Ids that don't exist on the endpoint are ignored; the response says how many were actually deleted.",
        "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" }
        }
      }
    }
  },
  "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": {
      "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"] }
      },
      "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" }
      }
    },
    "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" }
        }
      },
      "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" },
          "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" },
          "forwardUrl": { "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." },
          "responseContentType": { "type": "string", "default": "application/json" },
          "responseHeaders": { "type": ["object", "null"], "additionalProperties": { "type": "string" } },
          "responseBody": { "type": ["string", "null"] },
          "forwardingEnabled": { "type": "boolean", "default": false },
          "forwardUrl": { "type": ["string", "null"], "description": "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." }
        }
      },
      "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 },
          "responseContentType": { "type": ["string", "null"] },
          "responseHeaders": { "type": ["object", "null"], "additionalProperties": { "type": "string" } },
          "responseBody": { "type": ["string", "null"] },
          "isActive": { "type": ["boolean", "null"], "description": "false turns the endpoint dark: senders get 410." },
          "forwardingEnabled": { "type": ["boolean", "null"] },
          "forwardUrl": { "type": ["string", "null"] },
          "provider": { "type": ["string", "null"], "description": "Provider slug; empty string clears it, omitted leaves it unchanged." }
        }
      },
      "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"] },
          "tier": { "type": "integer", "description": "1 = full event-source and signature metadata; 2 = named and selectable." }
        }
      },
      "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" },
          "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" },
          "delivery": { "$ref": "#/components/schemas/Delivery" }
        }
      },
      "Delivery": {
        "type": "object",
        "description": "Latest delivery outcome for the request. `state` NotAttempted means forwarding was off when it arrived.",
        "properties": {
          "state": { "type": "string", "enum": ["NotAttempted", "Pending", "Succeeded", "Failed", "Errored", "DeadLetter"] },
          "statusCode": { "type": ["integer", "null"] },
          "error": { "type": ["string", "null"] },
          "url": { "type": ["string", "null"] },
          "at": { "type": ["string", "null"], "format": "date-time" },
          "durationMs": { "type": ["integer", "null"] }
        }
      },
      "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 — or Interrupted when a worker died mid-send." },
          "statusCode": { "type": ["integer", "null"] },
          "error": { "type": ["string", "null"] },
          "durationMs": { "type": ["integer", "null"] }
        }
      },
      "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 }
        }
      },
      "BulkReplayOutcome": {
        "type": "object",
        "properties": {
          "requestsEnqueued": { "type": "integer" },
          "jobsCreated": { "type": "integer" },
          "skippedTruncated": { "type": "integer", "description": "Requests whose stored body was an oversize placeholder — 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" }
        }
      }
    }
  }
}
