{
  "openapi": "3.1.0",
  "info": {
    "title": "Pushary API",
    "version": "1.0.0",
    "summary": "Human-in-the-loop and push notifications for AI agents.",
    "description": "The Pushary REST API. Use it to open a decision that blocks on a real person, evaluate an action against an authorization policy, enroll one of your own end-users for phone approvals, and send push notifications.\n\nThere are three ways in, all speaking to the same account:\n\n- **REST**, documented here. Base URL `https://pushary.com/api/v1/server`.\n- **MCP**, at `https://pushary.com/api/mcp/mcp` (Streamable HTTP). Handshake and tool catalogue are readable without a key.\n- **CLI**, `npx @pushary/agent-hooks setup`, which wires an agent up in one command.\n\nOfficial SDKs: `@pushary/server` (npm) and `pushary` (PyPI), plus framework adapters for the Vercel AI SDK, LangGraph, CrewAI, Mastra, Eve and the OpenAI Agents SDK.\n\nEvery error is JSON in the `Error` shape below: a stable human `error` sentence, a machine-readable `code`, a `hint`, and a `documentation` link. That includes the two an agent hits while finding its way: an unknown path returns `code: \"not_found\"` with a link back to this document, and an unsupported method returns `code: \"method_not_allowed\"` with an `Allow` header and an `allowed` array.",
    "termsOfService": "https://pushary.com/terms",
    "contact": {
      "name": "Pushary",
      "url": "https://pushary.com/docs",
      "email": "business@pushary.com"
    }
  },
  "externalDocs": {
    "description": "Pushary documentation",
    "url": "https://pushary.com/docs"
  },
  "servers": [
    {
      "url": "https://pushary.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Decisions",
      "description": "Ask a human, and read what they decided."
    },
    {
      "name": "Authorization",
      "description": "Evaluate an action against the workspace policy."
    },
    {
      "name": "Enrollment",
      "description": "Connect one of your end-users to phone approvals."
    },
    {
      "name": "Keys",
      "description": "Per-end-user API keys for multi-tenant agent runtimes."
    },
    {
      "name": "Notifications",
      "description": "Send a push notification."
    },
    {
      "name": "Subscribers",
      "description": "The people a site can notify."
    },
    {
      "name": "Campaigns",
      "description": "Scheduled and on-demand notification campaigns."
    },
    {
      "name": "Templates",
      "description": "Reusable notification content."
    },
    {
      "name": "Flows",
      "description": "Event-triggered notification automations."
    },
    {
      "name": "Agents",
      "description": "Machines agents run on."
    },
    {
      "name": "Account",
      "description": "Who this key is, and what it can reach."
    }
  ],
  "paths": {
    "/api/v1/server/identity": {
      "get": {
        "operationId": "getIdentity",
        "summary": "Identify the calling API key",
        "description": "Returns which key is calling, which site and workspace it belongs to, and the plan behind it. The cheapest way to check that a key is still valid and its subscription still active, which is what a CLI needs before it writes any config.",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "The calling key and its site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServerIdentity"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/site": {
      "get": {
        "operationId": "getSite",
        "summary": "Read the site this key belongs to",
        "description": "Returns the site this API key belongs to: its id, name, slug, and the hosted subscribe page URL for it.",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "The site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Site"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/channels": {
      "get": {
        "operationId": "listChannels",
        "summary": "Count the channels that can reach a human right now",
        "description": "Reports how many native app devices and browser subscriptions are live, split between the key owner and the whole site. Use it before opening a decision to tell \"nobody is reachable\" apart from \"the human declined\".",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Reachability counts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChannelCounts"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/machines": {
      "get": {
        "operationId": "listMachines",
        "summary": "List machines agents have run on",
        "description": "Returns the machines this site has seen an agent report from, most recent first.",
        "tags": [
          "Agents"
        ],
        "responses": {
          "200": {
            "description": "The machines seen for this site.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "machines"
                  ],
                  "properties": {
                    "machines": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Machine"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/send": {
      "post": {
        "operationId": "sendNotification",
        "summary": "Send a push notification",
        "description": "Sends a push notification to the site’s subscribers. With no `subscriberIds`, `externalIds` or `tags` it goes to every active subscriber. Supply `idempotencyKey` (body) or the `Idempotency-Key` header to make a retry safe; a replay returns the original result with `duplicate: true`.",
        "tags": [
          "Notifications"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Stable across retries of one logical send. Must match `idempotencyKey` in the body if both are supplied.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The notification to send.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendNotification"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The send was accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/subscribers": {
      "get": {
        "operationId": "listSubscribers",
        "summary": "List subscribers",
        "description": "Returns a cursor-paginated page of the site’s subscribers, newest first.",
        "tags": [
          "Subscribers"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Defaults to the server page size.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from the previous page’s `nextCursor`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only subscribers in this state.",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "unsubscribed",
                "expired",
                "bounced"
              ]
            }
          },
          {
            "name": "tags",
            "in": "query",
            "required": false,
            "description": "Comma-separated tags. Only subscribers holding every one.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "externalId",
            "in": "query",
            "required": false,
            "description": "Only subscribers carrying this external id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of subscribers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedSubscribers"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/subscribers/count": {
      "get": {
        "operationId": "countSubscribers",
        "summary": "Count subscribers",
        "description": "Returns total, active and unsubscribed counts for the site in one call.",
        "tags": [
          "Subscribers"
        ],
        "responses": {
          "200": {
            "description": "Subscriber counts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriberCount"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/subscribers/{subscriberId}": {
      "get": {
        "operationId": "getSubscriber",
        "summary": "Read one subscriber",
        "description": "Returns a single subscriber by its Pushary id, with its delivery status, device metadata and tags.",
        "tags": [
          "Subscribers"
        ],
        "parameters": [
          {
            "name": "subscriberId",
            "in": "path",
            "required": true,
            "description": "The Pushary subscriber id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The subscriber.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscriber"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updateSubscriber",
        "summary": "Update a subscriber",
        "description": "Changes the tags, external id or custom data held against a subscriber.",
        "tags": [
          "Subscribers"
        ],
        "parameters": [
          {
            "name": "subscriberId",
            "in": "path",
            "required": true,
            "description": "The Pushary subscriber id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Fields to change.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSubscriber"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated subscriber.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscriber"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteSubscriber",
        "summary": "Delete a subscriber",
        "description": "Permanently removes a subscriber and its push subscription.",
        "tags": [
          "Subscribers"
        ],
        "parameters": [
          {
            "name": "subscriberId",
            "in": "path",
            "required": true,
            "description": "The Pushary subscriber id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/campaigns": {
      "get": {
        "operationId": "listCampaigns",
        "summary": "List campaigns",
        "description": "Returns a cursor-paginated page of the site’s campaigns, newest first.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Defaults to the server page size.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from the previous page’s `nextCursor`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of campaigns.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedCampaigns"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "createCampaign",
        "summary": "Create a campaign",
        "description": "Creates a campaign in `draft`, or in `scheduled` when `scheduledAt` is set. Creating does not send: call sendCampaign for that.",
        "tags": [
          "Campaigns"
        ],
        "requestBody": {
          "required": true,
          "description": "The campaign to create.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCampaign"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created campaign.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Campaign"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/campaigns/{campaignId}": {
      "get": {
        "operationId": "getCampaign",
        "summary": "Read one campaign",
        "description": "Returns a single campaign by id, with its content, schedule, status and lifetime delivery counters.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "campaignId",
            "in": "path",
            "required": true,
            "description": "The campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The campaign.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Campaign"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updateCampaign",
        "summary": "Update a campaign",
        "description": "Changes the content, schedule or status of a campaign that has not completed.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "campaignId",
            "in": "path",
            "required": true,
            "description": "The campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Fields to change.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCampaign"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated campaign.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Campaign"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteCampaign",
        "summary": "Delete a campaign",
        "description": "Permanently removes a campaign. Deliveries already made are unaffected.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "campaignId",
            "in": "path",
            "required": true,
            "description": "The campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/campaigns/{campaignId}/send": {
      "post": {
        "operationId": "sendCampaign",
        "summary": "Send a campaign now",
        "description": "Queues an existing campaign for immediate delivery, ignoring any schedule on it.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "campaignId",
            "in": "path",
            "required": true,
            "description": "The campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The campaign was queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/campaigns/{campaignId}/pause": {
      "post": {
        "operationId": "pauseCampaign",
        "summary": "Pause a campaign",
        "description": "Stops a sending or scheduled campaign. Deliveries already queued still land.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "campaignId",
            "in": "path",
            "required": true,
            "description": "The campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The paused campaign.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Campaign"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/campaigns/{campaignId}/resume": {
      "post": {
        "operationId": "resumeCampaign",
        "summary": "Resume a campaign",
        "description": "Returns a paused campaign to its previous state.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "campaignId",
            "in": "path",
            "required": true,
            "description": "The campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The resumed campaign.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Campaign"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/campaigns/{campaignId}/stats": {
      "get": {
        "operationId": "getCampaignStats",
        "summary": "Read campaign delivery stats",
        "description": "Returns sent, delivered, clicked and failed counts plus the derived rates.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "campaignId",
            "in": "path",
            "required": true,
            "description": "The campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Campaign statistics.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignStats"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/notifications/{notificationId}": {
      "get": {
        "operationId": "getNotification",
        "summary": "Read one notification",
        "description": "Returns a single notification record by id, together with the delivery state the push service last reported for it.",
        "tags": [
          "Notifications"
        ],
        "parameters": [
          {
            "name": "notificationId",
            "in": "path",
            "required": true,
            "description": "The notification id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The notification.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/templates": {
      "get": {
        "operationId": "listTemplates",
        "summary": "List templates",
        "description": "Returns a cursor-paginated page of the site’s notification templates.",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Defaults to the server page size.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from the previous page’s `nextCursor`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of templates.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedTemplates"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "createTemplate",
        "summary": "Create a template",
        "description": "Creates a reusable notification template.",
        "tags": [
          "Templates"
        ],
        "requestBody": {
          "required": true,
          "description": "The template to create.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTemplate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created template.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/templates/{templateId}": {
      "get": {
        "operationId": "getTemplate",
        "summary": "Read one template",
        "description": "Returns a single template by id, with its title, body and image URLs.",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "name": "templateId",
            "in": "path",
            "required": true,
            "description": "The template id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The template.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updateTemplate",
        "summary": "Update a template",
        "description": "Changes the content of a template. Campaigns already created from it are unaffected.",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "name": "templateId",
            "in": "path",
            "required": true,
            "description": "The template id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Fields to change.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTemplate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated template.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteTemplate",
        "summary": "Delete a template",
        "description": "Permanently removes a template.",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "name": "templateId",
            "in": "path",
            "required": true,
            "description": "The template id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/flows": {
      "get": {
        "operationId": "listFlows",
        "summary": "List automation flows",
        "description": "Returns a cursor-paginated page of the site’s flows.",
        "tags": [
          "Flows"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Defaults to the server page size.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from the previous page’s `nextCursor`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only flows in this state.",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "active",
                "paused",
                "archived"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of flows.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedFlows"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "createFlow",
        "summary": "Create an automation flow",
        "description": "Creates a flow in `draft`. Call activateFlow to start it running.",
        "tags": [
          "Flows"
        ],
        "requestBody": {
          "required": true,
          "description": "The flow to create.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateFlow"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created flow.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Flow"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/flows/{flowId}": {
      "get": {
        "operationId": "getFlow",
        "summary": "Read one flow",
        "description": "Returns a single flow by id, including its trigger, status, execution count and its steps in order.",
        "tags": [
          "Flows"
        ],
        "parameters": [
          {
            "name": "flowId",
            "in": "path",
            "required": true,
            "description": "The flow id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The flow.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Flow"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updateFlow",
        "summary": "Update a flow",
        "description": "Changes a flow. Supplying `steps` replaces the whole step list.",
        "tags": [
          "Flows"
        ],
        "parameters": [
          {
            "name": "flowId",
            "in": "path",
            "required": true,
            "description": "The flow id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Fields to change.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateFlow"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated flow.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Flow"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteFlow",
        "summary": "Delete a flow",
        "description": "Permanently removes a flow and its steps.",
        "tags": [
          "Flows"
        ],
        "parameters": [
          {
            "name": "flowId",
            "in": "path",
            "required": true,
            "description": "The flow id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/flows/{flowId}/activate": {
      "post": {
        "operationId": "activateFlow",
        "summary": "Activate a flow",
        "description": "Moves a flow to `active` so its trigger starts firing.",
        "tags": [
          "Flows"
        ],
        "parameters": [
          {
            "name": "flowId",
            "in": "path",
            "required": true,
            "description": "The flow id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The activated flow.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Flow"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/flows/{flowId}/pause": {
      "post": {
        "operationId": "pauseFlow",
        "summary": "Pause a flow",
        "description": "Moves a flow to `paused`. Runs already in flight finish.",
        "tags": [
          "Flows"
        ],
        "parameters": [
          {
            "name": "flowId",
            "in": "path",
            "required": true,
            "description": "The flow id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The paused flow.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Flow"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/enroll": {
      "post": {
        "operationId": "enrollEndUser",
        "summary": "Enroll one of your end-users for phone approvals",
        "description": "Returns a single-use, short-lived link to show one of your own end-users. They tap it once to connect their phone: no Pushary account and no app install on their side. Cache the resulting enrollment against your `externalId`, not the link, which expires. Requires the Partner plan.",
        "tags": [
          "Enrollment"
        ],
        "requestBody": {
          "required": true,
          "description": "The end-user to enroll.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EnrollRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The enrollment link.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrollResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/decisions": {
      "post": {
        "operationId": "createDecision",
        "summary": "Ask a human to approve, deny or answer",
        "description": "Opens a decision and delivers it to a person: you, your team, or the end-user named by `externalId`. Returns immediately with a `decisionId` to poll, unless `wait` is true, in which case the request is held open until the decision resolves or `timeoutSeconds` passes. Fails closed: an unanswered decision is never an approval.",
        "tags": [
          "Decisions"
        ],
        "requestBody": {
          "required": true,
          "description": "The decision to open.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDecision"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The decision was opened, or replayed from an idempotency key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DecisionResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The addressed end-user cannot be reached and `requireReachable` was true.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/decisions/{decisionId}": {
      "get": {
        "operationId": "getDecision",
        "summary": "Read a decision",
        "description": "Returns the current state of a decision. Pass `wait=true` to long-poll until it resolves or the server timeout passes; the decision stays answerable for its full TTL either way.",
        "tags": [
          "Decisions"
        ],
        "parameters": [
          {
            "name": "decisionId",
            "in": "path",
            "required": true,
            "description": "The decision id returned by createDecision.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "description": "Hold the request open until the decision resolves.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The decision.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Decision"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "answerDecision",
        "summary": "Record an answer on a decision",
        "description": "Records a human answer server-side, for callers that collect the answer through their own surface instead of the hosted decision page.",
        "tags": [
          "Decisions"
        ],
        "parameters": [
          {
            "name": "decisionId",
            "in": "path",
            "required": true,
            "description": "The decision id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The answer to record.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnswerDecision"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The answer was recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DecisionAnswerResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "cancelDecision",
        "summary": "Cancel a pending decision",
        "description": "Retracts a decision that is no longer needed, so the person is not asked something that has stopped mattering.",
        "tags": [
          "Decisions"
        ],
        "parameters": [
          {
            "name": "decisionId",
            "in": "path",
            "required": true,
            "description": "The decision id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The decision was cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CancelDecisionResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/authorize": {
      "post": {
        "operationId": "authorizeAction",
        "summary": "Evaluate an action against the authorization policy",
        "description": "Runs an action through the workspace authorization rules and returns `allow`, `deny`, or `requires_human`. On `requires_human` the caller opens a decision (the SDK does this for you). Fail-closed: anything the rules do not allow is not allowed.",
        "tags": [
          "Authorization"
        ],
        "requestBody": {
          "required": true,
          "description": "The action to authorize.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AuthorizeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The policy verdict.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthorizationEvaluation"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/keys": {
      "get": {
        "operationId": "listBoundKeys",
        "summary": "List per-end-user API keys",
        "description": "Returns the bound keys issued from this account, including revoked ones.",
        "tags": [
          "Keys"
        ],
        "responses": {
          "200": {
            "description": "The bound keys.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "keys"
                  ],
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BoundKeySummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "issueBoundKey",
        "summary": "Issue a per-end-user API key",
        "description": "Mints a key bound to one end-user for one session. A bound key can only evaluate policy, create or resolve that user’s decisions, and enroll that user, so a leaked session key cannot reach your audience or mint further keys. The full key is returned once and cannot be retrieved again.",
        "tags": [
          "Keys"
        ],
        "requestBody": {
          "required": true,
          "description": "The end-user to bind the key to.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IssueBoundKey"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The issued key, shown once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BoundKeyResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/keys/{keyPrefix}": {
      "delete": {
        "operationId": "revokeBoundKey",
        "summary": "Revoke a per-end-user API key",
        "description": "Revokes a bound key immediately. In-flight requests holding it stop being authorized.",
        "tags": [
          "Keys"
        ],
        "parameters": [
          {
            "name": "keyPrefix",
            "in": "path",
            "required": true,
            "description": "The public half of the key to revoke.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The key was revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RevokeKeyResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/keys/self/revoke": {
      "post": {
        "operationId": "revokeCallingKey",
        "summary": "Revoke the calling key",
        "description": "Revokes the key that made this request. Lets an agent runtime destroy its own session credential when the session ends, without holding a second key that could revoke it.",
        "tags": [
          "Keys"
        ],
        "responses": {
          "200": {
            "description": "The calling key was revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RevokeKeyResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/server/webhook-secret": {
      "get": {
        "operationId": "getWebhookSecret",
        "summary": "Read the decision webhook secret",
        "description": "Returns the secret to verify `X-Pushary-Signature` against on decision callbacks. Verify before trusting a callback body.",
        "tags": [
          "Decisions"
        ],
        "responses": {
          "200": {
            "description": "The webhook secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSecret"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "rotateWebhookSecret",
        "summary": "Rotate the decision webhook secret",
        "description": "Issues a new webhook secret and invalidates the previous one. Callbacks signed with the old secret stop verifying immediately.",
        "tags": [
          "Decisions"
        ],
        "responses": {
          "200": {
            "description": "The new webhook secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSecret"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "A Pushary API key, sent as `Authorization: Bearer pk_xxx.sk_xxx`. Create one with `npx @pushary/agent-hooks setup` or in Dashboard > Agent > Settings. The MCP endpoint additionally accepts OAuth 2.0; see /.well-known/oauth-protected-resource."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every error response from this API is JSON in this shape. `error` is the human sentence and is stable across releases; `code` is the machine-readable classification to branch on.",
        "required": [
          "error",
          "code",
          "status",
          "message",
          "hint"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable summary. Stable: fields are added, never renamed."
          },
          "code": {
            "type": "string",
            "description": "Machine-readable classification.",
            "enum": [
              "not_found",
              "method_not_allowed",
              "unauthorized",
              "forbidden",
              "invalid_request",
              "rate_limited"
            ]
          },
          "status": {
            "type": "integer",
            "description": "The HTTP status code, repeated in the body for transports that hide it."
          },
          "message": {
            "type": "string",
            "description": "What went wrong, in one sentence."
          },
          "hint": {
            "type": "string",
            "description": "What to do about it."
          },
          "documentation": {
            "type": "string",
            "description": "URL of the documentation page for this rule.",
            "format": "uri"
          }
        }
      },
      "Subscriber": {
        "type": "object",
        "description": "One browser push subscription belonging to a site.",
        "required": [
          "id",
          "siteId",
          "endpoint",
          "status",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Pushary identifier for this subscriber."
          },
          "siteId": {
            "type": "string",
            "description": "The site this subscriber belongs to."
          },
          "endpoint": {
            "type": "string",
            "description": "The push service endpoint the browser issued."
          },
          "status": {
            "type": "string",
            "description": "Delivery state of the subscription.",
            "enum": [
              "active",
              "unsubscribed",
              "expired",
              "bounced"
            ]
          },
          "browser": {
            "type": "string",
            "description": "Browser name, derived from the user agent at subscribe time."
          },
          "os": {
            "type": "string",
            "description": "Operating system, derived from the user agent at subscribe time."
          },
          "deviceType": {
            "type": "string",
            "description": "Device class.",
            "enum": [
              "mobile",
              "tablet",
              "desktop"
            ]
          },
          "country": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 country, derived from the subscribe request."
          },
          "city": {
            "type": "string",
            "description": "City, derived from the subscribe request."
          },
          "timezone": {
            "type": "string",
            "description": "IANA timezone reported by the browser."
          },
          "language": {
            "type": "string",
            "description": "BCP 47 language reported by the browser."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Segmentation tags."
          },
          "externalId": {
            "type": "string",
            "description": "Your own identifier for this person, if you set one."
          },
          "createdAt": {
            "type": "string",
            "description": "When the subscription was created.",
            "format": "date-time"
          },
          "lastActiveAt": {
            "type": "string",
            "description": "When this subscriber was last seen.",
            "format": "date-time"
          }
        }
      },
      "UpdateSubscriber": {
        "type": "object",
        "description": "Fields that may be changed on an existing subscriber.",
        "properties": {
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Replaces the tag set."
          },
          "externalId": {
            "type": "string",
            "description": "Your own identifier for this person."
          },
          "customData": {
            "type": "object",
            "description": "Arbitrary JSON stored against the subscriber."
          }
        }
      },
      "SubscriberCount": {
        "type": "object",
        "required": [
          "total",
          "active",
          "unsubscribed"
        ],
        "properties": {
          "total": {
            "type": "integer",
            "description": "Every subscriber row on the site."
          },
          "active": {
            "type": "integer",
            "description": "Subscribers that can currently receive a notification."
          },
          "unsubscribed": {
            "type": "integer",
            "description": "Subscribers that opted out."
          }
        }
      },
      "SendNotification": {
        "type": "object",
        "description": "A push notification to send now. With no audience field the notification goes to every active subscriber on the site.",
        "required": [
          "title",
          "body"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "Notification title."
          },
          "body": {
            "type": "string",
            "description": "Notification body."
          },
          "iconUrl": {
            "type": "string",
            "description": "Icon image URL.",
            "format": "uri"
          },
          "imageUrl": {
            "type": "string",
            "description": "Hero image URL.",
            "format": "uri"
          },
          "url": {
            "type": "string",
            "description": "Where a click should land.",
            "format": "uri"
          },
          "subscriberIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Send only to these Pushary subscriber ids."
          },
          "externalIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Send only to subscribers carrying these external ids."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Send only to subscribers holding every one of these tags."
          },
          "data": {
            "type": "object",
            "description": "Arbitrary JSON delivered with the notification."
          },
          "metadata": {
            "type": "object",
            "description": "Arbitrary JSON recorded against the send."
          },
          "idempotencyKey": {
            "type": "string",
            "description": "Stable across retries of one logical send. Maximum 200 characters. May also be supplied as the Idempotency-Key header; supplying both with different values is a 400.",
            "maxLength": 200
          }
        }
      },
      "SendResult": {
        "type": "object",
        "required": [
          "success",
          "sent",
          "limits"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "description": "True when the send was accepted for delivery."
          },
          "campaignId": {
            "type": "string",
            "description": "The campaign row this send produced."
          },
          "status": {
            "type": "string",
            "description": "Delivery state at the moment of the response.",
            "enum": [
              "queued"
            ]
          },
          "queued": {
            "type": "integer",
            "description": "How many recipients were queued."
          },
          "duplicate": {
            "type": "boolean",
            "description": "True when an idempotency key replayed an earlier send."
          },
          "sent": {
            "type": "integer",
            "description": "Deprecated. This is the queued recipient count, not a device receipt. Read `queued`.",
            "deprecated": true
          },
          "limits": {
            "type": "object",
            "required": [
              "remaining",
              "limit"
            ],
            "description": "Plan quota after this send.",
            "properties": {
              "remaining": {
                "type": "integer",
                "description": "Notifications left in the current window."
              },
              "limit": {
                "type": "integer",
                "description": "Notifications allowed in the window."
              }
            }
          }
        }
      },
      "Campaign": {
        "type": "object",
        "required": [
          "id",
          "siteId",
          "name",
          "title",
          "body",
          "status",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Campaign identifier."
          },
          "siteId": {
            "type": "string",
            "description": "The site this campaign belongs to."
          },
          "name": {
            "type": "string",
            "description": "Internal name."
          },
          "title": {
            "type": "string",
            "description": "Notification title."
          },
          "body": {
            "type": "string",
            "description": "Notification body."
          },
          "iconUrl": {
            "type": "string",
            "description": "Icon image URL.",
            "format": "uri"
          },
          "imageUrl": {
            "type": "string",
            "description": "Hero image URL.",
            "format": "uri"
          },
          "actionUrl": {
            "type": "string",
            "description": "Where a click should land.",
            "format": "uri"
          },
          "status": {
            "type": "string",
            "description": "Lifecycle state.",
            "enum": [
              "draft",
              "scheduled",
              "sending",
              "active",
              "paused",
              "completed",
              "cancelled"
            ]
          },
          "scheduledAt": {
            "type": "string",
            "description": "When the campaign is due to send.",
            "format": "date-time"
          },
          "totalTargeted": {
            "type": "integer",
            "description": "Recipients selected."
          },
          "totalSent": {
            "type": "integer",
            "description": "Recipients the push service accepted."
          },
          "totalDelivered": {
            "type": "integer",
            "description": "Deliveries confirmed by the service worker."
          },
          "totalClicked": {
            "type": "integer",
            "description": "Clicks recorded."
          },
          "createdAt": {
            "type": "string",
            "description": "When the campaign was created.",
            "format": "date-time"
          }
        }
      },
      "CreateCampaign": {
        "type": "object",
        "required": [
          "name",
          "title",
          "body"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Internal name."
          },
          "title": {
            "type": "string",
            "description": "Notification title."
          },
          "body": {
            "type": "string",
            "description": "Notification body."
          },
          "iconUrl": {
            "type": "string",
            "description": "Icon image URL.",
            "format": "uri"
          },
          "imageUrl": {
            "type": "string",
            "description": "Hero image URL.",
            "format": "uri"
          },
          "actionUrl": {
            "type": "string",
            "description": "Where a click should land.",
            "format": "uri"
          },
          "scheduledAt": {
            "type": "string",
            "description": "Send at this time instead of on demand.",
            "format": "date-time"
          },
          "segmentId": {
            "type": "string",
            "description": "Restrict the audience to a saved segment."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Restrict the audience to subscribers holding every one of these tags."
          }
        }
      },
      "UpdateCampaign": {
        "type": "object",
        "description": "Every field optional. Only what is present is changed.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Internal name."
          },
          "title": {
            "type": "string",
            "description": "Notification title."
          },
          "body": {
            "type": "string",
            "description": "Notification body."
          },
          "iconUrl": {
            "type": "string",
            "description": "Icon image URL.",
            "format": "uri"
          },
          "imageUrl": {
            "type": "string",
            "description": "Hero image URL.",
            "format": "uri"
          },
          "actionUrl": {
            "type": "string",
            "description": "Where a click should land.",
            "format": "uri"
          },
          "scheduledAt": {
            "type": "string",
            "description": "Send at this time instead of on demand.",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "description": "Move the campaign between these states.",
            "enum": [
              "draft",
              "paused",
              "cancelled"
            ]
          }
        }
      },
      "CampaignStats": {
        "type": "object",
        "required": [
          "totalSent",
          "totalDelivered",
          "totalClicked",
          "totalFailed",
          "deliveryRate",
          "clickRate"
        ],
        "properties": {
          "totalSent": {
            "type": "integer",
            "description": "Recipients the push service accepted."
          },
          "totalDelivered": {
            "type": "integer",
            "description": "Deliveries confirmed by the service worker."
          },
          "totalClicked": {
            "type": "integer",
            "description": "Clicks recorded."
          },
          "totalFailed": {
            "type": "integer",
            "description": "Sends the push service rejected."
          },
          "deliveryRate": {
            "type": "number",
            "description": "Delivered divided by sent, 0 to 1."
          },
          "clickRate": {
            "type": "number",
            "description": "Clicked divided by delivered, 0 to 1."
          }
        }
      },
      "Template": {
        "type": "object",
        "required": [
          "id",
          "siteId",
          "name",
          "title",
          "body",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Template identifier."
          },
          "siteId": {
            "type": "string",
            "description": "The site this template belongs to."
          },
          "name": {
            "type": "string",
            "description": "Internal name."
          },
          "title": {
            "type": "string",
            "description": "Notification title."
          },
          "body": {
            "type": "string",
            "description": "Notification body."
          },
          "iconUrl": {
            "type": "string",
            "description": "Icon image URL.",
            "format": "uri"
          },
          "imageUrl": {
            "type": "string",
            "description": "Hero image URL.",
            "format": "uri"
          },
          "actionUrl": {
            "type": "string",
            "description": "Where a click should land.",
            "format": "uri"
          },
          "createdAt": {
            "type": "string",
            "description": "When the template was created.",
            "format": "date-time"
          }
        }
      },
      "CreateTemplate": {
        "type": "object",
        "required": [
          "name",
          "title",
          "body"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Internal name."
          },
          "title": {
            "type": "string",
            "description": "Notification title."
          },
          "body": {
            "type": "string",
            "description": "Notification body."
          },
          "iconUrl": {
            "type": "string",
            "description": "Icon image URL.",
            "format": "uri"
          },
          "imageUrl": {
            "type": "string",
            "description": "Hero image URL.",
            "format": "uri"
          },
          "actionUrl": {
            "type": "string",
            "description": "Where a click should land.",
            "format": "uri"
          }
        }
      },
      "UpdateTemplate": {
        "type": "object",
        "description": "Every field optional. Only what is present is changed.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Internal name."
          },
          "title": {
            "type": "string",
            "description": "Notification title."
          },
          "body": {
            "type": "string",
            "description": "Notification body."
          },
          "iconUrl": {
            "type": "string",
            "description": "Icon image URL.",
            "format": "uri"
          },
          "imageUrl": {
            "type": "string",
            "description": "Hero image URL.",
            "format": "uri"
          },
          "actionUrl": {
            "type": "string",
            "description": "Where a click should land.",
            "format": "uri"
          }
        }
      },
      "FlowStepConfig": {
        "type": "object",
        "description": "Configuration for one step. Which key is read depends on the step type.",
        "properties": {
          "notification": {
            "type": "object",
            "required": [
              "title",
              "body"
            ],
            "description": "Read by a send_notification step.",
            "properties": {
              "title": {
                "type": "string",
                "description": "Notification title."
              },
              "body": {
                "type": "string",
                "description": "Notification body."
              },
              "iconUrl": {
                "type": "string",
                "description": "Icon image URL.",
                "format": "uri"
              },
              "imageUrl": {
                "type": "string",
                "description": "Hero image URL.",
                "format": "uri"
              },
              "actionUrl": {
                "type": "string",
                "description": "Where a click should land.",
                "format": "uri"
              }
            }
          },
          "delay": {
            "type": "object",
            "required": [
              "value",
              "unit"
            ],
            "description": "Read by a delay step.",
            "properties": {
              "value": {
                "type": "integer",
                "description": "How long to wait."
              },
              "unit": {
                "type": "string",
                "description": "Unit of the wait.",
                "enum": [
                  "seconds",
                  "minutes",
                  "hours",
                  "days"
                ]
              }
            }
          }
        }
      },
      "FlowStep": {
        "type": "object",
        "required": [
          "id",
          "flowId",
          "stepOrder",
          "stepType",
          "config",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Step identifier."
          },
          "flowId": {
            "type": "string",
            "description": "The flow this step belongs to."
          },
          "stepOrder": {
            "type": "integer",
            "description": "Position in the flow, ascending."
          },
          "stepType": {
            "type": "string",
            "description": "What the step does.",
            "enum": [
              "send_notification",
              "delay",
              "exit"
            ]
          },
          "config": {
            "$ref": "#/components/schemas/FlowStepConfig"
          },
          "createdAt": {
            "type": "string",
            "description": "When the step was created.",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "description": "When the step was last changed.",
            "format": "date-time"
          }
        }
      },
      "Flow": {
        "type": "object",
        "required": [
          "id",
          "siteId",
          "name",
          "triggerType",
          "status",
          "executionCount",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Flow identifier."
          },
          "siteId": {
            "type": "string",
            "description": "The site this flow belongs to."
          },
          "name": {
            "type": "string",
            "description": "Internal name."
          },
          "description": {
            "type": "string",
            "description": "What the flow is for."
          },
          "triggerType": {
            "type": "string",
            "description": "The event that starts the flow.",
            "enum": [
              "notification_click",
              "notification_impression",
              "notification_dismiss",
              "subscription",
              "unsubscription"
            ]
          },
          "triggerCampaignId": {
            "type": "string",
            "description": "Restrict the trigger to one campaign."
          },
          "status": {
            "type": "string",
            "description": "Lifecycle state.",
            "enum": [
              "draft",
              "active",
              "paused",
              "archived"
            ]
          },
          "executionCount": {
            "type": "integer",
            "description": "How many times the flow has run."
          },
          "createdBy": {
            "type": "string",
            "description": "The user who created the flow."
          },
          "createdAt": {
            "type": "string",
            "description": "When the flow was created.",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "description": "When the flow was last changed.",
            "format": "date-time"
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FlowStep"
            },
            "description": "Steps in order."
          }
        }
      },
      "FlowStepInput": {
        "type": "object",
        "required": [
          "stepType",
          "config"
        ],
        "properties": {
          "stepType": {
            "type": "string",
            "description": "What the step does.",
            "enum": [
              "send_notification",
              "delay",
              "exit"
            ]
          },
          "config": {
            "$ref": "#/components/schemas/FlowStepConfig"
          }
        }
      },
      "CreateFlow": {
        "type": "object",
        "required": [
          "name",
          "triggerType"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Internal name."
          },
          "description": {
            "type": "string",
            "description": "What the flow is for."
          },
          "triggerType": {
            "type": "string",
            "description": "The event that starts the flow.",
            "enum": [
              "notification_click",
              "notification_impression",
              "notification_dismiss",
              "subscription",
              "unsubscription"
            ]
          },
          "triggerCampaignId": {
            "type": "string",
            "description": "Restrict the trigger to one campaign."
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FlowStepInput"
            },
            "description": "Steps in order."
          }
        }
      },
      "UpdateFlow": {
        "type": "object",
        "description": "Every field optional. Only what is present is changed.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Internal name."
          },
          "description": {
            "type": "string",
            "description": "What the flow is for."
          },
          "triggerType": {
            "type": "string",
            "description": "The event that starts the flow.",
            "enum": [
              "notification_click",
              "notification_impression",
              "notification_dismiss",
              "subscription",
              "unsubscription"
            ]
          },
          "triggerCampaignId": {
            "type": "string",
            "description": "Restrict the trigger to one campaign."
          },
          "status": {
            "type": "string",
            "description": "Lifecycle state.",
            "enum": [
              "draft",
              "active",
              "paused",
              "archived"
            ]
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FlowStepInput"
            },
            "description": "Replaces the step list."
          }
        }
      },
      "DecisionSubject": {
        "type": "object",
        "description": "What a decision is about. Every field optional: a free-text question alone is a complete decision. Supplying these lets the decision be grouped, risk-classified and matched against a permission rule.",
        "properties": {
          "toolName": {
            "type": "string",
            "description": "The action, e.g. `refund.create` or `database.write`."
          },
          "toolTarget": {
            "type": "string",
            "description": "What the action acts on."
          },
          "actor": {
            "type": "string",
            "description": "Whose authority the action claims, e.g. `user:u_44`. Not necessarily the approver. Maximum 120 characters.",
            "maxLength": 120
          },
          "environment": {
            "type": "string",
            "description": "Which deployment the action runs against, e.g. `production`. Maximum 40 characters.",
            "maxLength": 40
          },
          "parameters": {
            "type": "object",
            "description": "Typed arguments of the action, e.g. `{ \"amount\": 4800, \"currency\": \"EUR\" }`. Scalars only, at most 32 keys. A non-scalar value rejects the whole request with 400 rather than being dropped: a rule that reads a missing parameter can only ever decide more permissively than one that reads it.",
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean"
              ]
            }
          }
        }
      },
      "CreateDecision": {
        "allOf": [
          {
            "$ref": "#/components/schemas/DecisionSubject"
          },
          {
            "type": "object",
            "required": [
              "question"
            ],
            "properties": {
              "question": {
                "type": "string",
                "description": "What the person is being asked."
              },
              "type": {
                "type": "string",
                "description": "How the person answers.",
                "enum": [
                  "confirm",
                  "select",
                  "input"
                ]
              },
              "options": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "The choices, for a `select` decision."
              },
              "externalId": {
                "type": "string",
                "description": "Your own id for the end-user who should be asked."
              },
              "email": {
                "type": "string",
                "description": "Approver's email. With Slack connected the approval is DM'd to that person instead of the shared channel.",
                "format": "email"
              },
              "callbackUrl": {
                "type": "string",
                "description": "Pushary POSTs the resolved decision here, signed with X-Pushary-Signature.",
                "format": "uri"
              },
              "agentName": {
                "type": "string",
                "description": "Shown to the person so they know which agent is asking."
              },
              "context": {
                "type": "string",
                "description": "Free text echoed back on read and on the webhook."
              },
              "placeholder": {
                "type": "string",
                "description": "Placeholder text, for an `input` decision."
              },
              "expiresInSeconds": {
                "type": "integer",
                "description": "How long the decision stays answerable."
              },
              "wait": {
                "type": "boolean",
                "description": "Hold the request open until the decision resolves or the timeout passes."
              },
              "timeoutSeconds": {
                "type": "integer",
                "description": "How long to hold the request open when `wait` is true."
              },
              "idempotencyKey": {
                "type": "string",
                "description": "Stable across retries of one logical decision."
              },
              "poweredBy": {
                "type": "boolean",
                "description": "Show Pushary branding on the hosted decision page."
              },
              "requireReachable": {
                "type": "boolean",
                "description": "Refuse with 409 instead of opening a decision the end-user cannot receive, when the externalId has no enrolled device or push subscription."
              }
            }
          }
        ]
      },
      "DecisionResult": {
        "type": "object",
        "description": "What creating a decision returns.",
        "required": [
          "decisionId",
          "status",
          "answered"
        ],
        "properties": {
          "decisionId": {
            "type": "string",
            "description": "Identifier to poll, answer or cancel."
          },
          "status": {
            "type": "string",
            "description": "Lifecycle state.",
            "enum": [
              "pending",
              "answered",
              "expired",
              "cancelled"
            ]
          },
          "answered": {
            "type": "boolean",
            "description": "True once a person has answered."
          },
          "value": {
            "type": [
              "string",
              "null"
            ],
            "description": "The person's answer, once answered."
          },
          "type": {
            "type": "string",
            "description": "How the person answers.",
            "enum": [
              "confirm",
              "select",
              "input"
            ]
          },
          "question": {
            "type": "string",
            "description": "What the person was asked."
          },
          "pollUrl": {
            "type": "string",
            "description": "Where to poll for the answer.",
            "format": "uri"
          },
          "decisionPageUrl": {
            "type": "string",
            "description": "The hosted page the person answers on.",
            "format": "uri"
          },
          "expiresInSeconds": {
            "type": "integer",
            "description": "How long the decision stays answerable."
          },
          "idempotent": {
            "type": "boolean",
            "description": "True when an idempotency key replayed an earlier decision."
          },
          "hint": {
            "type": "string",
            "description": "Advice when the decision may not reach anyone."
          },
          "reachable": {
            "type": "boolean",
            "description": "Whether the addressed end-user can be reached at create time."
          },
          "reachableChannels": {
            "type": "integer",
            "description": "How many channels can reach them."
          },
          "deviceCount": {
            "type": "integer",
            "description": "How many devices are enrolled for them."
          }
        }
      },
      "Decision": {
        "type": "object",
        "description": "A decision as read back.",
        "required": [
          "decisionId",
          "status",
          "answered",
          "value",
          "type",
          "question",
          "createdAt"
        ],
        "properties": {
          "decisionId": {
            "type": "string",
            "description": "Identifier."
          },
          "status": {
            "type": "string",
            "description": "Lifecycle state.",
            "enum": [
              "pending",
              "answered",
              "expired",
              "cancelled"
            ]
          },
          "answered": {
            "type": "boolean",
            "description": "True once a person has answered."
          },
          "value": {
            "type": [
              "string",
              "null"
            ],
            "description": "The person's answer, or null while pending."
          },
          "type": {
            "type": "string",
            "description": "How the person answers.",
            "enum": [
              "confirm",
              "select",
              "input"
            ]
          },
          "question": {
            "type": "string",
            "description": "What the person was asked."
          },
          "options": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "The choices, for a `select` decision."
          },
          "externalId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your own id for the end-user who was asked."
          },
          "context": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free text passed at create time."
          },
          "createdAt": {
            "type": "string",
            "description": "When the decision was opened.",
            "format": "date-time"
          },
          "answeredAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When it was answered."
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When it stops being answerable."
          }
        }
      },
      "AnswerDecision": {
        "type": "object",
        "required": [
          "answer"
        ],
        "properties": {
          "answer": {
            "type": "string",
            "description": "The answer to record. For a `confirm` decision, `yes` or `no`."
          }
        }
      },
      "DecisionAnswerResult": {
        "type": "object",
        "required": [
          "decisionId",
          "status",
          "answered"
        ],
        "properties": {
          "decisionId": {
            "type": "string",
            "description": "Identifier."
          },
          "status": {
            "type": "string",
            "description": "Lifecycle state after recording.",
            "enum": [
              "pending",
              "answered",
              "expired",
              "cancelled"
            ]
          },
          "answered": {
            "type": "boolean",
            "description": "True once a person has answered."
          },
          "value": {
            "type": [
              "string",
              "null"
            ],
            "description": "The recorded answer."
          }
        }
      },
      "CancelDecisionResult": {
        "type": "object",
        "required": [
          "decisionId",
          "cancelled",
          "status"
        ],
        "properties": {
          "decisionId": {
            "type": "string",
            "description": "Identifier."
          },
          "cancelled": {
            "type": "boolean",
            "description": "True when this call cancelled it."
          },
          "status": {
            "type": "string",
            "description": "Lifecycle state after cancelling."
          }
        }
      },
      "AuthorizeRequest": {
        "allOf": [
          {
            "$ref": "#/components/schemas/DecisionSubject"
          },
          {
            "type": "object",
            "required": [
              "toolName"
            ],
            "properties": {
              "toolName": {
                "type": "string",
                "description": "The action being authorized. Required: a rule has to name it."
              },
              "externalId": {
                "type": "string",
                "description": "Your own id for the end-user this action belongs to."
              },
              "question": {
                "type": "string",
                "description": "Shown to the person if policy defers to one. Defaults to the action and its target."
              },
              "agentName": {
                "type": "string",
                "description": "Shown to the person so they know which agent is asking."
              },
              "timeoutMs": {
                "type": "integer",
                "description": "How long to wait on a person, when policy defers to one."
              },
              "expiresInSeconds": {
                "type": "integer",
                "description": "How long the decision stays answerable."
              },
              "idempotencyKey": {
                "type": "string",
                "description": "Stable across retries of one logical authorization."
              },
              "requireReachable": {
                "type": "boolean",
                "description": "Refuse with 409 rather than ask a person who cannot be reached."
              }
            }
          }
        ]
      },
      "AuthorizationEvaluation": {
        "type": "object",
        "description": "The raw policy verdict, before a `requires_human` verdict is resolved by asking a person.",
        "required": [
          "verdict",
          "policy",
          "reason",
          "authorizationId"
        ],
        "properties": {
          "verdict": {
            "type": "string",
            "description": "What policy decided.",
            "enum": [
              "allow",
              "deny",
              "requires_human"
            ]
          },
          "policy": {
            "type": [
              "string",
              "null"
            ],
            "description": "The rule that decided, or null when no rule named the action."
          },
          "reason": {
            "type": "string",
            "description": "Why."
          },
          "authorizationId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The audit row this evaluation produced."
          }
        }
      },
      "EnrollRequest": {
        "type": "object",
        "required": [
          "externalId"
        ],
        "properties": {
          "externalId": {
            "type": "string",
            "description": "Your own identifier for the end-user to enroll."
          }
        }
      },
      "EnrollResult": {
        "type": "object",
        "description": "A single-use, short-lived link to show one of your end-users so they can turn on phone approvals in one tap. Cache the resulting enrollment, not this link.",
        "required": [
          "externalId",
          "token",
          "deepLink",
          "universalLink",
          "expiresInSeconds"
        ],
        "properties": {
          "externalId": {
            "type": "string",
            "description": "The identifier you supplied."
          },
          "token": {
            "type": "string",
            "description": "The single-use enrollment token."
          },
          "deepLink": {
            "type": "string",
            "description": "Native app deep link (pushary://enroll?token=...)."
          },
          "universalLink": {
            "type": "string",
            "description": "HTTPS link safe to render as a button, QR code, or send over email, SMS or Slack. Opens the app if installed, else a web-push fallback page.",
            "format": "uri"
          },
          "expiresInSeconds": {
            "type": "integer",
            "description": "How long the link stays valid."
          }
        }
      },
      "IssueBoundKey": {
        "type": "object",
        "required": [
          "externalId"
        ],
        "properties": {
          "externalId": {
            "type": "string",
            "description": "The end-user this key is bound to."
          },
          "name": {
            "type": "string",
            "description": "A label for the key."
          },
          "expiresInSeconds": {
            "type": "integer",
            "description": "How long the key stays valid."
          }
        }
      },
      "BoundKeyResult": {
        "type": "object",
        "required": [
          "apiKey",
          "keyPrefix",
          "scope",
          "boundExternalId",
          "expiresAt"
        ],
        "properties": {
          "apiKey": {
            "type": "string",
            "description": "The full key, shown once. It cannot be retrieved again."
          },
          "keyPrefix": {
            "type": "string",
            "description": "The public half, used to revoke the key later."
          },
          "scope": {
            "type": "string",
            "description": "Always `bound`.",
            "enum": [
              "bound"
            ]
          },
          "boundExternalId": {
            "type": "string",
            "description": "The end-user this key is bound to."
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When it expires."
          }
        }
      },
      "BoundKeySummary": {
        "type": "object",
        "required": [
          "keyPrefix",
          "name",
          "boundExternalId",
          "isActive",
          "lastUsedAt",
          "expiresAt",
          "createdAt"
        ],
        "properties": {
          "keyPrefix": {
            "type": "string",
            "description": "The public half of the key."
          },
          "name": {
            "type": "string",
            "description": "The label given at issue time."
          },
          "boundExternalId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The end-user this key is bound to."
          },
          "isActive": {
            "type": "boolean",
            "description": "False once revoked or expired."
          },
          "lastUsedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Last use."
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When it expires."
          },
          "createdAt": {
            "type": "string",
            "description": "When it was issued.",
            "format": "date-time"
          }
        }
      },
      "RevokeKeyResult": {
        "type": "object",
        "required": [
          "keyPrefix",
          "revoked"
        ],
        "properties": {
          "keyPrefix": {
            "type": "string",
            "description": "The key that was revoked."
          },
          "revoked": {
            "type": "boolean",
            "description": "True when this call revoked it."
          }
        }
      },
      "WebhookSecret": {
        "type": "object",
        "required": [
          "webhookSecret"
        ],
        "properties": {
          "webhookSecret": {
            "type": "string",
            "description": "The secret to verify X-Pushary-Signature against on decision callbacks."
          }
        }
      },
      "ServerIdentity": {
        "type": "object",
        "description": "Who this API key is: which key, which site, which plan.",
        "required": [
          "keyId",
          "keyPrefix",
          "keyScope",
          "siteId",
          "siteSlug",
          "plan",
          "locked"
        ],
        "properties": {
          "keyId": {
            "type": "string",
            "description": "Key identifier."
          },
          "keyPrefix": {
            "type": "string",
            "description": "The public half of the key."
          },
          "keyName": {
            "type": "string",
            "description": "The label given at creation."
          },
          "keyScope": {
            "type": "string",
            "description": "What the key may reach.",
            "enum": [
              "full",
              "connector",
              "bound"
            ]
          },
          "siteId": {
            "type": "string",
            "description": "The site the key belongs to."
          },
          "siteSlug": {
            "type": "string",
            "description": "The site slug."
          },
          "siteName": {
            "type": "string",
            "description": "The site name."
          },
          "workspaceId": {
            "type": "string",
            "description": "The workspace the site belongs to."
          },
          "plan": {
            "type": "string",
            "description": "The billing plan behind the key."
          },
          "surfacePlan": {
            "type": "string",
            "description": "Which product surface the plan belongs to."
          },
          "subscriptionStatus": {
            "type": "string",
            "description": "Stripe subscription status, or `none`."
          },
          "locked": {
            "type": "boolean",
            "description": "True when the workspace is locked for billing."
          },
          "machineId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The machine this key was last used from."
          },
          "authorizedUserId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The user whose session minted the key."
          },
          "workspaceMembers": {
            "type": "integer",
            "description": "How many people are in the workspace."
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When it was created."
          },
          "lastUsedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When it was last used."
          }
        }
      },
      "Site": {
        "type": "object",
        "required": [
          "id",
          "name",
          "slug"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Site identifier."
          },
          "name": {
            "type": "string",
            "description": "Site name."
          },
          "slug": {
            "type": "string",
            "description": "Site slug."
          },
          "subscribeUrl": {
            "type": "string",
            "description": "The hosted subscribe page for this site.",
            "format": "uri"
          }
        }
      },
      "ChannelCounts": {
        "type": "object",
        "description": "How many ways a notification can reach someone right now.",
        "properties": {
          "app": {
            "type": "object",
            "description": "Native app devices."
          },
          "web": {
            "type": "object",
            "description": "Browser push subscriptions."
          },
          "phoneReachable": {
            "type": "boolean",
            "description": "True when at least one phone can be reached."
          },
          "reachable": {
            "type": "boolean",
            "description": "True when any channel at all can be reached."
          },
          "yours": {
            "type": "object",
            "description": "The same counts restricted to the key owner."
          },
          "site": {
            "type": "object",
            "description": "The same counts across the whole site."
          },
          "owner": {
            "type": [
              "string",
              "null"
            ],
            "description": "The user who minted the key."
          }
        }
      },
      "Machine": {
        "type": "object",
        "description": "A machine an agent has run on, as last reported.",
        "properties": {
          "machineId": {
            "type": "string",
            "description": "Machine identifier."
          },
          "name": {
            "type": "string",
            "description": "Human label for the machine."
          },
          "lastSeenAt": {
            "type": "string",
            "description": "When the machine last reported in.",
            "format": "date-time"
          }
        }
      },
      "PaginatedSubscribers": {
        "type": "object",
        "required": [
          "data",
          "hasMore"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Subscriber"
            },
            "description": "This page of subscribers."
          },
          "nextCursor": {
            "type": "string",
            "description": "Pass as `cursor` to fetch the next page."
          },
          "hasMore": {
            "type": "boolean",
            "description": "True when another page exists."
          }
        }
      },
      "PaginatedCampaigns": {
        "type": "object",
        "required": [
          "data",
          "hasMore"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Campaign"
            },
            "description": "This page of campaigns."
          },
          "nextCursor": {
            "type": "string",
            "description": "Pass as `cursor` to fetch the next page."
          },
          "hasMore": {
            "type": "boolean",
            "description": "True when another page exists."
          }
        }
      },
      "PaginatedTemplates": {
        "type": "object",
        "required": [
          "data",
          "hasMore"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Template"
            },
            "description": "This page of templates."
          },
          "nextCursor": {
            "type": "string",
            "description": "Pass as `cursor` to fetch the next page."
          },
          "hasMore": {
            "type": "boolean",
            "description": "True when another page exists."
          }
        }
      },
      "PaginatedFlows": {
        "type": "object",
        "required": [
          "data",
          "hasMore"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Flow"
            },
            "description": "This page of flows."
          },
          "nextCursor": {
            "type": "string",
            "description": "Pass as `cursor` to fetch the next page."
          },
          "hasMore": {
            "type": "boolean",
            "description": "True when another page exists."
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request body or parameters were rejected.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The API key is missing, malformed or revoked.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key is valid but not allowed to do this: wrong scope, wrong plan, or a locked workspace.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such resource, or no such endpoint.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests. `X-RateLimit-Remaining` and `X-RateLimit-Reset` are on every response.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}