openapi: 3.1.0 info: title: BeeBuzz API version: 0.9.0 description: | BeeBuzz public API reference. Send push notifications, manage webhooks, and retrieve paired device public keys for end-to-end payload encryption. servers: - url: / tags: - name: Health - name: Auth - name: User - name: Usage - name: Topics - name: Devices - name: Tokens - name: Push - name: Webhooks - name: Admin - name: Attachments paths: /health: get: tags: [Health] operationId: checkHealth summary: Health check x-audience: [public] x-stability: stable responses: "200": description: OK content: application/json: schema: $ref: "#/components/schemas/HealthResponse" /v1/health: get: tags: [Health] operationId: checkApiHealth summary: API health check x-audience: [public] x-stability: stable responses: "200": description: OK content: application/json: schema: $ref: "#/components/schemas/HealthResponse" /v1/openapi.yaml: get: tags: [Health] operationId: getOpenAPIYAML summary: Get the OpenAPI document description: Returns the canonical OpenAPI contract published by this API server. x-audience: [public] x-stability: stable responses: "200": description: OpenAPI YAML document content: application/yaml: schema: type: string /v1/vapid-public-key: get: tags: [Push] operationId: getVapidPublicKey summary: Get the VAPID public key x-audience: [hive] x-stability: stable responses: "200": description: OK content: application/json: schema: $ref: "#/components/schemas/VAPIDPublicKeyResponse" /v1/auth/login: post: tags: [Auth] operationId: login summary: Start OTP login description: Supported client fields are documented here. The server may apply additional anti-abuse checks that are intentionally excluded from the public request contract. x-audience: [site] x-stability: stable requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/LoginRequest" responses: "204": description: OTP challenge accepted "400": $ref: "#/components/responses/BadRequest" "413": $ref: "#/components/responses/PayloadTooLarge" "422": $ref: "#/components/responses/ValidationError" "429": description: Too many auth attempts headers: Retry-After: schema: type: string content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "500": $ref: "#/components/responses/InternalError" /v1/auth/otp/verify: post: tags: [Auth] operationId: verifyOtp summary: Verify OTP and create a session x-audience: [site] x-stability: stable requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/VerifyOTPRequest" responses: "200": description: OTP verified headers: Set-Cookie: schema: type: string description: Session and logged-in cookies are set on success content: application/json: schema: $ref: "#/components/schemas/MessageResponse" "400": $ref: "#/components/responses/BadRequest" "413": $ref: "#/components/responses/PayloadTooLarge" "401": $ref: "#/components/responses/Unauthorized" "422": $ref: "#/components/responses/ValidationError" "429": $ref: "#/components/responses/TooManyRequests" "500": $ref: "#/components/responses/InternalError" /v1/auth/logout: post: tags: [Auth] operationId: logout summary: Revoke the current session x-audience: [site] x-stability: stable security: - cookieAuth: [] responses: "204": description: Logged out headers: Set-Cookie: schema: type: string description: Session and logged-in cookies are cleared on success "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /v1/me: get: tags: [User] operationId: getMe summary: Get current user profile x-audience: [site] x-stability: stable security: - cookieAuth: [] responses: "200": description: Current user content: application/json: schema: $ref: "#/components/schemas/UserResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /v1/me/usage: get: tags: [Usage] operationId: getAccountUsage summary: Get account usage analytics x-audience: [site] x-stability: stable security: - cookieAuth: [] parameters: - in: query name: days schema: type: integer minimum: 0 maximum: 90 description: 0 means all time, otherwise 1 to 90 responses: "200": description: Account usage data content: application/json: schema: $ref: "#/components/schemas/AccountUsageResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /v1/topics: get: tags: [Topics] operationId: listTopics summary: List topics x-audience: [site] x-stability: stable security: - cookieAuth: [] responses: "200": description: Topic list content: application/json: schema: $ref: "#/components/schemas/TopicsListResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" post: tags: [Topics] operationId: createTopic summary: Create a topic x-audience: [site] x-stability: stable security: - cookieAuth: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CreateTopicRequest" responses: "201": description: Topic created content: application/json: schema: $ref: "#/components/schemas/TopicResponse" "400": $ref: "#/components/responses/BadRequest" "413": $ref: "#/components/responses/PayloadTooLarge" "409": $ref: "#/components/responses/Conflict" "422": $ref: "#/components/responses/ValidationError" "500": $ref: "#/components/responses/InternalError" /v1/topics/{topicID}: patch: tags: [Topics] operationId: updateTopic summary: Update a topic x-audience: [site] x-stability: stable security: - cookieAuth: [] parameters: - $ref: "#/components/parameters/TopicID" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UpdateTopicRequest" responses: "204": description: Topic updated "400": $ref: "#/components/responses/BadRequest" "413": $ref: "#/components/responses/PayloadTooLarge" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" "422": $ref: "#/components/responses/ValidationError" "500": $ref: "#/components/responses/InternalError" delete: tags: [Topics] operationId: deleteTopic summary: Delete a topic x-audience: [site] x-stability: stable security: - cookieAuth: [] parameters: - $ref: "#/components/parameters/TopicID" responses: "204": description: Topic deleted "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/Conflict" "500": $ref: "#/components/responses/InternalError" /v1/devices: get: tags: [Devices] operationId: listDevices summary: List devices description: Device responses expose topic IDs in the `topic_ids` array. x-audience: [site] x-stability: stable security: - cookieAuth: [] responses: "200": description: Device list content: application/json: schema: $ref: "#/components/schemas/DevicesListResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" post: tags: [Devices] operationId: createDevice summary: Create a device and pairing code x-audience: [site] x-stability: stable security: - cookieAuth: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CreateDeviceRequest" responses: "201": description: Device created content: application/json: schema: $ref: "#/components/schemas/CreatedDeviceResponse" "400": $ref: "#/components/responses/BadRequest" "413": $ref: "#/components/responses/PayloadTooLarge" "422": $ref: "#/components/responses/UnprocessableEntity" "500": $ref: "#/components/responses/InternalError" /v1/devices/{deviceID}: patch: tags: [Devices] operationId: updateDevice summary: Update a device x-audience: [site] x-stability: stable security: - cookieAuth: [] parameters: - $ref: "#/components/parameters/DeviceID" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UpdateDeviceRequest" responses: "204": description: Device updated "400": $ref: "#/components/responses/BadRequest" "413": $ref: "#/components/responses/PayloadTooLarge" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" "422": $ref: "#/components/responses/UnprocessableEntity" "500": $ref: "#/components/responses/InternalError" delete: tags: [Devices] operationId: deleteDevice summary: Delete a device x-audience: [site] x-stability: stable security: - cookieAuth: [] parameters: - $ref: "#/components/parameters/DeviceID" responses: "204": description: Device deleted "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /v1/devices/{deviceID}/pairing-code: post: tags: [Devices] operationId: regeneratePairingCode summary: Regenerate a device pairing code x-audience: [site] x-stability: stable security: - cookieAuth: [] parameters: - $ref: "#/components/parameters/DeviceID" responses: "200": description: Pairing code regenerated content: application/json: schema: $ref: "#/components/schemas/PairingCodeResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /v1/devices/{deviceID}/unpair: post: tags: [Devices] operationId: unpairDevice summary: Remove a device push subscription x-audience: [site] x-stability: stable security: - cookieAuth: [] parameters: - $ref: "#/components/parameters/DeviceID" responses: "204": description: Device unpaired "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /v1/pairing: post: tags: [Devices] operationId: pairDevice summary: Pair a device with a push subscription x-audience: [hive] x-stability: stable requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/PairRequest" responses: "200": description: Device paired content: application/json: schema: $ref: "#/components/schemas/PairResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "422": $ref: "#/components/responses/UnprocessableEntity" "429": $ref: "#/components/responses/TooManyRequests" "500": $ref: "#/components/responses/InternalError" /v1/pairing/{deviceID}: get: tags: [Devices] operationId: getPairingStatus summary: Get the pairing status for a paired device x-audience: [hive] x-stability: stable parameters: - in: path name: deviceID required: true schema: type: string - in: header name: Authorization required: true schema: type: string description: Bearer device token returned by `POST /v1/pairing` responses: "200": description: Pairing status content: application/json: schema: $ref: "#/components/schemas/PairingStatusResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "429": $ref: "#/components/responses/TooManyRequests" "500": $ref: "#/components/responses/InternalError" /v1/tokens: get: tags: [Tokens] operationId: listApiTokens summary: List API tokens description: API token responses expose topic IDs in the `topic_ids` array. x-audience: [site] x-stability: stable security: - cookieAuth: [] responses: "200": description: API token list content: application/json: schema: $ref: "#/components/schemas/APITokensListResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" post: tags: [Tokens] operationId: createApiToken summary: Create an API token x-audience: [site] x-stability: stable security: - cookieAuth: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CreateAPITokenRequest" responses: "201": description: API token created content: application/json: schema: $ref: "#/components/schemas/CreatedAPITokenResponse" "400": $ref: "#/components/responses/BadRequest" "422": $ref: "#/components/responses/UnprocessableEntity" "500": $ref: "#/components/responses/InternalError" /v1/tokens/{tokenID}: patch: tags: [Tokens] operationId: updateApiToken summary: Update an API token x-audience: [site] x-stability: stable security: - cookieAuth: [] parameters: - $ref: "#/components/parameters/TokenID" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UpdateAPITokenRequest" responses: "204": description: API token updated "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" "422": $ref: "#/components/responses/UnprocessableEntity" "500": $ref: "#/components/responses/InternalError" delete: tags: [Tokens] operationId: revokeApiToken summary: Revoke an API token x-audience: [site] x-stability: stable security: - cookieAuth: [] parameters: - $ref: "#/components/parameters/TokenID" responses: "204": description: API token revoked "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /v1/push: post: tags: [Push] operationId: sendNotification summary: Send a notification x-audience: [public] x-stability: stable description: | Trusted path — send application/json or multipart/form-data with plaintext title and body. The server handles delivery to paired devices. E2EE path — retrieve device age public keys from GET /v1/push/keys, encrypt your payload client-side, then send as application/octet-stream. Use the X-Priority header to set priority. security: - bearerAuth: [] parameters: - $ref: "#/components/parameters/XPriority" requestBody: required: true content: application/json: description: Trusted notification with plaintext title and body. schema: $ref: "#/components/schemas/SendRequest" multipart/form-data: description: Trusted notification with file attachment. schema: $ref: "#/components/schemas/SendMultipartRequest" application/octet-stream: description: | E2EE encrypted payload. Encrypt with age public keys from `GET /v1/push/keys` and send the raw ciphertext. Priority is set via the `X-Priority` header. schema: type: string format: binary responses: "200": description: Notification send result content: application/json: schema: $ref: "#/components/schemas/SendResponse" "400": $ref: "#/components/responses/BadRequest" "413": $ref: "#/components/responses/PayloadTooLarge" "401": $ref: "#/components/responses/Unauthorized" "422": description: Validation or attachment processing error content: application/json: schema: oneOf: - $ref: "#/components/schemas/ValidationErrorResponse" - $ref: "#/components/schemas/ErrorResponse" examples: validation_error: value: code: validation_error errors: - title: is required attachment_processing_failed: value: code: attachment_processing_failed message: Failed to process attachment "429": $ref: "#/components/responses/TooManyRequests" "502": description: Push delivery failed for all devices content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: code: push_delivery_failed message: Push delivery failed for all devices "500": $ref: "#/components/responses/InternalError" /v1/push/{topic}: post: tags: [Push] operationId: sendNotificationToTopic summary: Send a notification to a topic x-audience: [public] x-stability: stable description: | Trusted path — send application/json or multipart/form-data with plaintext title and body. The server handles delivery to paired devices. E2EE path — retrieve device age public keys from GET /v1/push/keys, encrypt your payload client-side, then send as application/octet-stream. Use the X-Priority header to set priority. security: - bearerAuth: [] parameters: - in: path name: topic required: true schema: type: string maxLength: 32 pattern: '^[a-z][a-z0-9_]{0,31}$' - $ref: "#/components/parameters/XPriority" requestBody: required: true content: application/json: description: Trusted notification with plaintext title and body. schema: $ref: "#/components/schemas/SendRequest" multipart/form-data: description: Trusted notification with file attachment. schema: $ref: "#/components/schemas/SendMultipartRequest" application/octet-stream: description: | E2EE encrypted payload. Encrypt with age public keys from `GET /v1/push/keys` and send the raw ciphertext. Priority is set via the `X-Priority` header. schema: type: string format: binary responses: "200": description: Notification send result content: application/json: schema: $ref: "#/components/schemas/SendResponse" "400": $ref: "#/components/responses/BadRequest" "413": $ref: "#/components/responses/PayloadTooLarge" "401": $ref: "#/components/responses/Unauthorized" "422": description: Validation or attachment processing error content: application/json: schema: oneOf: - $ref: "#/components/schemas/ValidationErrorResponse" - $ref: "#/components/schemas/ErrorResponse" examples: validation_error: value: code: validation_error errors: - title: is required attachment_processing_failed: value: code: attachment_processing_failed message: Failed to process attachment "429": $ref: "#/components/responses/TooManyRequests" "502": description: Push delivery failed for all devices content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: code: push_delivery_failed message: Push delivery failed for all devices "500": $ref: "#/components/responses/InternalError" /v1/push/keys: get: tags: [Push] operationId: listDevicePublicKeys summary: List device public keys description: | Returns the age public keys for all paired devices. Use these keys to encrypt push payloads for E2EE delivery via POST /v1/push or POST /v1/push/{topic} (send as application/octet-stream). x-audience: [public] x-stability: stable security: - bearerAuth: [] responses: "200": description: Device keys content: application/json: schema: $ref: "#/components/schemas/KeysResponse" "401": $ref: "#/components/responses/Unauthorized" "429": $ref: "#/components/responses/TooManyRequests" "500": $ref: "#/components/responses/InternalError" /v1/webhooks: get: tags: [Webhooks] operationId: listWebhooks summary: List webhooks description: Webhook responses expose topic IDs in the `topic_ids` array. x-audience: [site] x-stability: stable security: - cookieAuth: [] responses: "200": description: Webhook list content: application/json: schema: $ref: "#/components/schemas/WebhooksListResponse" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" post: tags: [Webhooks] operationId: createWebhook summary: Create a webhook x-audience: [site] x-stability: stable security: - cookieAuth: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CreateWebhookRequest" responses: "201": description: Webhook created content: application/json: schema: $ref: "#/components/schemas/CreatedWebhookResponse" "400": $ref: "#/components/responses/BadRequest" "413": $ref: "#/components/responses/PayloadTooLarge" "422": $ref: "#/components/responses/UnprocessableEntity" "500": $ref: "#/components/responses/InternalError" /v1/webhooks/{webhookID}: patch: tags: [Webhooks] operationId: updateWebhook summary: Update a webhook x-audience: [site] x-stability: stable security: - cookieAuth: [] parameters: - $ref: "#/components/parameters/WebhookID" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UpdateWebhookRequest" responses: "204": description: Webhook updated "400": $ref: "#/components/responses/BadRequest" "413": $ref: "#/components/responses/PayloadTooLarge" "401": $ref: "#/components/responses/Unauthorized" "422": $ref: "#/components/responses/UnprocessableEntity" "500": $ref: "#/components/responses/InternalError" delete: tags: [Webhooks] operationId: deleteWebhook summary: Delete a webhook x-audience: [site] x-stability: stable security: - cookieAuth: [] parameters: - $ref: "#/components/parameters/WebhookID" responses: "204": description: Webhook deleted "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /v1/webhooks/{webhookID}/token: post: tags: [Webhooks] operationId: regenerateWebhookToken summary: Regenerate a webhook token x-audience: [site] x-stability: stable security: - cookieAuth: [] parameters: - $ref: "#/components/parameters/WebhookID" responses: "200": description: Token regenerated content: application/json: schema: $ref: "#/components/schemas/RegenerateTokenResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /v1/webhooks/{token}: post: tags: [Webhooks] operationId: sendWebhookNotification summary: Send a notification from a webhook x-audience: [public] x-stability: stable parameters: - in: path name: token required: true schema: type: string requestBody: required: true content: application/json: schema: type: object additionalProperties: true example: title: "Build succeeded" body: "All tests passed in 12s" responses: "200": description: Webhook delivered content: application/json: schema: $ref: "#/components/schemas/ReceiveResponse" "202": description: Payload captured in inspect mode content: application/json: schema: $ref: "#/components/schemas/ReceiveResponse" "400": $ref: "#/components/responses/BadRequest" "413": $ref: "#/components/responses/PayloadTooLarge" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "422": $ref: "#/components/responses/UnprocessableEntity" "429": $ref: "#/components/responses/TooManyRequests" "502": description: Webhook delivery failed for all topics content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: code: webhook_delivery_failed message: Webhook dispatch failed for all topics "500": $ref: "#/components/responses/InternalError" /v1/webhooks/inspect: post: tags: [Webhooks] operationId: createInspectSession summary: Create an inspect session for discovering payload paths x-audience: [site] x-stability: stable description: | Creates a temporary inspect session that captures the next webhook payload without dispatching notifications. Use this to discover the correct title_path and body_path values for custom webhooks. security: - cookieAuth: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CreateInspectSessionRequest" responses: "201": description: Inspect session created content: application/json: schema: $ref: "#/components/schemas/InspectSessionResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "422": $ref: "#/components/responses/UnprocessableEntity" "500": $ref: "#/components/responses/InternalError" get: tags: [Webhooks] operationId: getInspectSession summary: Get the current inspect session status x-audience: [site] x-stability: stable description: | Returns the current inspect session status. Poll this endpoint until the status changes to "captured" to get the payload. security: - cookieAuth: [] responses: "200": description: Inspect session status content: application/json: schema: $ref: "#/components/schemas/InspectSessionStatusResponse" "401": $ref: "#/components/responses/Unauthorized" "404": description: No active inspect session found content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "500": $ref: "#/components/responses/InternalError" /v1/webhooks/inspect/finalize: post: tags: [Webhooks] operationId: finalizeInspectSession summary: Finalize an inspect session and create the webhook x-audience: [site] x-stability: stable description: | Creates the actual webhook with the specified title_path and body_path. The payload is validated against these paths before creating the webhook. Returns the new webhook token (one-time reveal). security: - cookieAuth: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/FinalizeInspectRequest" responses: "201": description: Webhook created from inspect session content: application/json: schema: $ref: "#/components/schemas/CreatedWebhookResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "404": description: Inspect session not found or expired content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "422": description: Payload extraction failed or no payload captured content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "500": $ref: "#/components/responses/InternalError" /v1/attachments/{token}: get: tags: [Attachments] operationId: getAttachment summary: Download an attachment by opaque token x-audience: [hive] x-stability: stable description: Public token-based download of the stored encrypted blob. Requests are rate-limited per attachment token. parameters: - in: path name: token required: true schema: type: string responses: "200": description: Attachment bytes content: application/octet-stream: schema: type: string format: binary "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /v1/admin/users: get: tags: [Admin] operationId: listAdminUsers summary: List users for the admin panel x-audience: [admin] x-stability: stable security: - cookieAuth: [] responses: "200": description: User list content: application/json: schema: $ref: "#/components/schemas/AdminUsersListResponse" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" /v1/admin/users/{userID}: patch: tags: [Admin] operationId: updateAdminUserStatus summary: Update user account status x-audience: [admin] x-stability: stable security: - cookieAuth: [] parameters: - in: path name: userID required: true schema: type: string requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UpdateUserStatusRequest" responses: "200": description: User updated content: application/json: schema: $ref: "#/components/schemas/AdminUserResponse" "400": description: Bad request content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: invalid_status: value: code: invalid_status message: "account_status must be one of: pending, active, blocked" invalid_transition: value: code: invalid_transition message: "invalid status transition" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "409": description: Conflict content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: code: concurrent_modification message: User status has changed. Re-fetch current status before retrying. "500": $ref: "#/components/responses/InternalError" /v1/admin/dashboard: get: tags: [Admin] operationId: getAdminDashboard summary: Get platform dashboard metrics x-audience: [admin] x-stability: stable security: - cookieAuth: [] parameters: - in: query name: days schema: type: integer minimum: 0 maximum: 90 description: 0 means all time, otherwise 1 to 90 responses: "200": description: Dashboard metrics content: application/json: schema: $ref: "#/components/schemas/PlatformDashboardResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" /v1/admin/system/notifications: get: tags: [Admin] operationId: getAdminSystemNotifications summary: Get system notification settings x-audience: [admin] x-stability: experimental security: - cookieAuth: [] responses: "200": description: System notification settings content: application/json: schema: $ref: "#/components/schemas/SystemNotificationSettingsResponse" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" patch: tags: [Admin] operationId: updateAdminSystemNotifications summary: Update system notification settings x-audience: [admin] x-stability: experimental security: - cookieAuth: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UpdateSystemNotificationSettingsRequest" responses: "200": description: System notification settings updated content: application/json: schema: $ref: "#/components/schemas/SystemNotificationSettingsResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "422": $ref: "#/components/responses/ValidationError" "500": $ref: "#/components/responses/InternalError" /_stub/push/next: get: tags: [Push] operationId: getNextPushStubEvent summary: Long-poll the next captured push event (push-stub mode) description: | Returns the next push event captured by the push-stub broker. Intended for local development and automated test flows only. Rejects non-loopback clients. x-audience: [internal] x-stability: internal responses: "200": description: A captured push event content: application/json: schema: $ref: "#/components/schemas/PushStubEvent" "204": description: No event available within the long-poll timeout "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" components: securitySchemes: cookieAuth: type: apiKey in: cookie name: beebuzz_session bearerAuth: type: http scheme: bearer parameters: TopicID: in: path name: topicID required: true schema: type: string DeviceID: in: path name: deviceID required: true schema: type: string TokenID: in: path name: tokenID required: true schema: type: string WebhookID: in: path name: webhookID required: true schema: type: string XPriority: in: header name: X-Priority required: false schema: type: string enum: [normal, high] description: | Push delivery priority. Relevant for `application/octet-stream` requests, where priority is passed via header instead of JSON body. responses: BadRequest: description: Bad request content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" Unauthorized: description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" Forbidden: description: Forbidden content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" NotFound: description: Not found content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" Conflict: description: Conflict content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" TooManyRequests: description: Too many requests content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" PayloadTooLarge: description: Payload too large content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" ValidationError: description: Validation error content: application/json: schema: $ref: "#/components/schemas/ValidationErrorResponse" UnprocessableEntity: description: Unprocessable entity content: application/json: schema: oneOf: - $ref: "#/components/schemas/ValidationErrorResponse" - $ref: "#/components/schemas/ErrorResponse" InternalError: description: Internal server error content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" schemas: ErrorResponse: type: object required: [code, message] properties: code: type: string message: type: string minLength: 1 ValidationErrorResponse: type: object required: [code, errors] properties: code: type: string enum: [validation_error] errors: type: array minItems: 1 items: type: string HealthResponse: type: object required: [status, version] properties: status: type: string example: ok version: type: string example: "0.9.0" MessageResponse: type: object required: [message] properties: message: type: string UserResponse: type: object required: [id, email, is_admin, account_status, created_at, updated_at] properties: id: type: string email: type: string format: email is_admin: type: boolean account_status: type: string enum: [pending, active, blocked] trial_started_at: type: - string - "null" format: date-time created_at: type: string format: date-time updated_at: type: string format: date-time LoginRequest: type: object required: [email, state] properties: email: type: string format: email state: type: string reason: type: - string - "null" VerifyOTPRequest: type: object required: [otp, state] properties: otp: type: string state: type: string TopicResponse: type: object required: [id, name, created_at, updated_at] properties: id: type: string name: type: string maxLength: 32 pattern: '^[a-z][a-z0-9_]{0,31}$' description: type: - string - "null" maxLength: 128 created_at: type: string format: date-time updated_at: type: string format: date-time TopicsListResponse: type: object required: [data] properties: data: type: array items: $ref: "#/components/schemas/TopicResponse" CreateTopicRequest: type: object required: [name] properties: name: type: string minLength: 1 maxLength: 32 pattern: '^[a-z][a-z0-9_]{0,31}$' description: type: string maxLength: 128 UpdateTopicRequest: type: object properties: description: type: string maxLength: 128 DeviceResponse: type: object required: [id, name, description, is_active, pairing_status, topic_ids, created_at, updated_at] properties: id: type: string name: type: string description: type: string is_active: type: boolean pairing_status: $ref: "#/components/schemas/PairingStatus" paired_at: type: - string - "null" format: date-time age_recipient: type: - string - "null" age_recipient_fingerprint: type: - string - "null" topic_ids: type: array items: type: string created_at: type: string format: date-time updated_at: type: string format: date-time DevicesListResponse: type: object required: [data] properties: data: type: array items: $ref: "#/components/schemas/DeviceResponse" DeviceKeyDescriptor: type: object required: [device_id, device_name, paired_at, age_recipient, age_recipient_fingerprint] properties: device_id: type: string device_name: type: string paired_at: type: string format: date-time age_recipient: type: string example: "age1..." age_recipient_fingerprint: type: string example: "a1b2c3d4" CreatedDeviceResponse: type: object required: [device, pairing_code, pairing_url, qr_code, expires_at] properties: device: $ref: "#/components/schemas/DeviceResponse" pairing_code: type: string pairing_url: type: string qr_code: type: string expires_at: type: string format: date-time PairingCodeResponse: type: object required: [pairing_code, pairing_url, qr_code, expires_at] properties: pairing_code: type: string pairing_url: type: string qr_code: type: string expires_at: type: string format: date-time CreateDeviceRequest: type: object required: [name, topics] properties: name: type: string minLength: 1 maxLength: 64 description: type: string maxLength: 128 topics: type: array items: type: string UpdateDeviceRequest: type: object required: [name, topics] properties: name: type: string minLength: 1 maxLength: 64 description: type: string maxLength: 128 topics: type: array items: type: string PairRequest: type: object required: [pairing_code, endpoint, p256dh, auth, age_recipient] properties: pairing_code: type: string endpoint: type: string p256dh: type: string auth: type: string age_recipient: type: string PairResponse: type: object required: [device_id, device_token] properties: device_id: type: string device_token: type: string PairingStatusResponse: type: object required: [device_id, pairing_status] properties: device_id: type: string pairing_status: $ref: "#/components/schemas/PairingStatus" PairingStatus: type: string enum: [pending, paired, unpaired, subscription_gone] APITokenResponse: type: object required: [id, name, last_four, created_at, is_active] properties: id: type: string name: type: string description: type: - string - "null" last_four: type: string topic_ids: type: array items: type: string description: Topic IDs allowed for this token. created_at: type: string format: date-time expires_at: type: - string - "null" format: date-time last_used_at: type: - string - "null" format: date-time is_active: type: boolean APITokensListResponse: type: object required: [data] properties: data: type: array items: $ref: "#/components/schemas/APITokenResponse" CreateAPITokenRequest: type: object required: [name, topics] properties: name: type: string minLength: 1 maxLength: 64 description: type: string maxLength: 64 topics: type: array items: type: string UpdateAPITokenRequest: type: object required: [name, topics] properties: name: type: string minLength: 1 maxLength: 64 description: type: string maxLength: 64 topics: type: array items: type: string CreatedAPITokenResponse: type: object required: [token, name] properties: token: type: string name: type: string WebhookResponse: type: object required: [id, name, payload_type, priority, is_active, created_at] properties: id: type: string name: type: string description: type: - string - "null" payload_type: type: string enum: [beebuzz, custom] title_path: type: string body_path: type: string priority: type: string enum: [normal, high] is_active: type: boolean created_at: type: string format: date-time last_used_at: type: - string - "null" format: date-time topic_ids: type: array items: type: string description: Topic IDs attached to this webhook. WebhooksListResponse: type: object required: [data] properties: data: type: array items: $ref: "#/components/schemas/WebhookResponse" CreateWebhookRequest: oneOf: - $ref: "#/components/schemas/CreateWebhookRequestBeebuzz" - $ref: "#/components/schemas/CreateWebhookRequestCustom" discriminator: propertyName: payload_type mapping: beebuzz: "#/components/schemas/CreateWebhookRequestBeebuzz" custom: "#/components/schemas/CreateWebhookRequestCustom" UpdateWebhookRequest: oneOf: - $ref: "#/components/schemas/UpdateWebhookRequestBeebuzz" - $ref: "#/components/schemas/UpdateWebhookRequestCustom" discriminator: propertyName: payload_type mapping: beebuzz: "#/components/schemas/UpdateWebhookRequestBeebuzz" custom: "#/components/schemas/UpdateWebhookRequestCustom" CreateWebhookRequestBeebuzz: type: object required: [name, payload_type, topics] properties: name: type: string minLength: 1 maxLength: 64 description: type: string maxLength: 128 payload_type: type: string const: beebuzz title_path: type: string maxLength: 0 body_path: type: string maxLength: 0 priority: type: string enum: [normal, high] default: normal topics: type: array items: type: string CreateWebhookRequestCustom: type: object required: [name, payload_type, title_path, body_path, topics] properties: name: type: string minLength: 1 maxLength: 64 description: type: string maxLength: 128 payload_type: type: string const: custom title_path: type: string minLength: 1 pattern: '^[A-Za-z0-9_]+(?:\[[0-9]+\])*(?:\.[A-Za-z0-9_]+(?:\[[0-9]+\])*)*$' description: Dot-separated JSON path with optional numeric array indexes. body_path: type: string minLength: 1 pattern: '^[A-Za-z0-9_]+(?:\[[0-9]+\])*(?:\.[A-Za-z0-9_]+(?:\[[0-9]+\])*)*$' description: Dot-separated JSON path with optional numeric array indexes. priority: type: string enum: [normal, high] default: normal topics: type: array items: type: string UpdateWebhookRequestBeebuzz: type: object required: [name, payload_type, topics] properties: name: type: string minLength: 1 maxLength: 64 description: type: string maxLength: 128 payload_type: type: string const: beebuzz title_path: type: string maxLength: 0 body_path: type: string maxLength: 0 priority: type: string enum: [normal, high] default: normal topics: type: array items: type: string UpdateWebhookRequestCustom: type: object required: [name, payload_type, title_path, body_path, topics] properties: name: type: string minLength: 1 maxLength: 64 description: type: string maxLength: 128 payload_type: type: string const: custom title_path: type: string minLength: 1 pattern: '^[A-Za-z0-9_]+(?:\[[0-9]+\])*(?:\.[A-Za-z0-9_]+(?:\[[0-9]+\])*)*$' description: Dot-separated JSON path with optional numeric array indexes. body_path: type: string minLength: 1 pattern: '^[A-Za-z0-9_]+(?:\[[0-9]+\])*(?:\.[A-Za-z0-9_]+(?:\[[0-9]+\])*)*$' description: Dot-separated JSON path with optional numeric array indexes. priority: type: string enum: [normal, high] default: normal topics: type: array items: type: string CreatedWebhookResponse: type: object required: [id, token, name] properties: id: type: string token: type: string name: type: string RegenerateTokenResponse: type: object required: [token] properties: token: type: string CreateInspectSessionRequest: type: object required: [name, topics] properties: name: type: string description: type: string priority: type: string enum: [normal, high] default: normal topics: type: array items: type: string InspectSessionResponse: type: object required: [token, url, status, expires_at] properties: token: type: string description: Temporary inspect token (beebuzz_whi_...) url: type: string format: uri description: Full webhook URL to send test payload status: type: string enum: [waiting] expires_at: type: string format: date-time InspectSessionStatusResponse: type: object required: [status, expires_at] properties: status: type: string enum: [waiting, captured] payload: type: object description: The captured webhook payload (only when status is "captured") captured_at: type: string format: date-time expires_at: type: string format: date-time FinalizeInspectRequest: type: object required: [title_path, body_path] properties: title_path: type: string minLength: 1 pattern: '^[A-Za-z0-9_]+(?:\[[0-9]+\])*(?:\.[A-Za-z0-9_]+(?:\[[0-9]+\])*)*$' description: Dot-separated JSON path with optional numeric array indexes for extracting title from payload. body_path: type: string minLength: 1 pattern: '^[A-Za-z0-9_]+(?:\[[0-9]+\])*(?:\.[A-Za-z0-9_]+(?:\[[0-9]+\])*)*$' description: Dot-separated JSON path with optional numeric array indexes for extracting body from payload. ReceiveResponse: type: object required: [status, sent_count, total_count, failed_count] properties: status: type: string example: delivered enum: [delivered, partial] sent_count: type: integer total_count: type: integer failed_count: type: integer SendRequest: type: object required: [title] properties: title: type: string minLength: 1 maxLength: 64 example: "Hello from BeeBuzz!" body: type: string maxLength: 256 example: "This is a test notification" priority: type: string enum: [normal, high] attachment_url: type: string format: uri SendMultipartRequest: type: object required: [title] properties: title: type: string minLength: 1 maxLength: 64 example: "Hello from BeeBuzz!" body: type: string maxLength: 256 example: "This is a test notification" priority: type: string enum: [normal, high] attachment: type: string format: binary SendResponse: type: object required: [status, sent_count, total_count, failed_count] properties: status: type: string enum: [delivered, partial] sent_count: type: integer total_count: type: integer failed_count: type: integer device_keys: type: array items: $ref: "#/components/schemas/DeviceKeyDescriptor" KeysResponse: type: object required: [data] properties: data: type: array items: $ref: "#/components/schemas/DeviceKeyDescriptor" VAPIDPublicKeyResponse: type: object required: [key] properties: key: type: string example: "BC1aL..." AccountUsageDayResponse: type: object required: [date, notifications_created, delivery_attempts, deliveries_succeeded, deliveries_failed, devices_lost, notifications_with_attachment, attachment_bytes_total, notifications_server_trusted, notifications_e2e, sources_cli, sources_webhook, sources_api, sources_internal] properties: date: type: string format: date notifications_created: type: integer delivery_attempts: type: integer deliveries_succeeded: type: integer deliveries_failed: type: integer devices_lost: type: integer notifications_with_attachment: type: integer attachment_bytes_total: type: integer notifications_server_trusted: type: integer notifications_e2e: type: integer sources_cli: type: integer sources_webhook: type: integer sources_api: type: integer sources_internal: type: integer AccountUsageResponse: type: object required: [data] properties: data: type: array items: $ref: "#/components/schemas/AccountUsageDayResponse" DailyUsageSummaryResponse: type: object required: [date, notifications_created, delivery_attempts, deliveries_succeeded, deliveries_failed, notifications_with_attachment, attachment_bytes_total, notifications_server_trusted, notifications_e2e, sources_cli, sources_webhook, sources_api, sources_internal, devices_lost, updated_at] properties: date: type: string format: date notifications_created: type: integer delivery_attempts: type: integer deliveries_succeeded: type: integer deliveries_failed: type: integer notifications_with_attachment: type: integer attachment_bytes_total: type: integer notifications_server_trusted: type: integer notifications_e2e: type: integer sources_cli: type: integer sources_webhook: type: integer sources_api: type: integer sources_internal: type: integer devices_lost: type: integer updated_at: type: string format: date-time PlatformDashboardResponse: type: object required: [notifications_created, delivery_attempts, deliveries_succeeded, deliveries_failed, delivery_success_rate, active_users, notifications_server_trusted, notifications_e2e, notifications_with_attachment, attachment_bytes_total, sources_cli, sources_webhook, sources_api, sources_internal, devices_lost, daily_breakdown] properties: notifications_created: type: integer delivery_attempts: type: integer deliveries_succeeded: type: integer deliveries_failed: type: integer delivery_success_rate: type: number format: double active_users: type: integer notifications_server_trusted: type: integer notifications_e2e: type: integer notifications_with_attachment: type: integer attachment_bytes_total: type: integer sources_cli: type: integer sources_webhook: type: integer sources_api: type: integer sources_internal: type: integer devices_lost: type: integer daily_breakdown: type: array items: $ref: "#/components/schemas/DailyUsageSummaryResponse" AdminUserResponse: type: object required: [id, email, is_admin, account_status, created_at, updated_at] properties: id: type: string email: type: string format: email is_admin: type: boolean account_status: type: string enum: [pending, active, blocked] signup_reason: type: - string - "null" trial_started_at: type: - string - "null" format: date-time created_at: type: string format: date-time updated_at: type: string format: date-time UpdateUserStatusRequest: type: object required: [account_status] properties: account_status: type: string enum: [pending, active, blocked] AdminUsersListResponse: type: object required: [data] properties: data: type: array items: $ref: "#/components/schemas/AdminUserResponse" SystemNotificationSettingsResponse: type: object required: [enabled, signup_created_enabled, recipient_has_active_device_for_topic] properties: enabled: type: boolean recipient_user_id: type: string topic_id: type: string signup_created_enabled: type: boolean recipient_has_active_device_for_topic: type: boolean description: | Best-effort flag computed at read time. True when the recipient admin has at least one paired device subscribed to the configured topic. Used by the admin UI to warn about a misconfiguration that would silently drop deliveries. created_at: type: string format: date-time updated_at: type: string format: date-time UpdateSystemNotificationSettingsRequest: type: object required: [enabled, topic_id, signup_created_enabled] properties: enabled: type: boolean topic_id: type: string signup_created_enabled: type: boolean PushStubEvent: type: object required: [endpoint, device_id, data] properties: endpoint: type: string description: The push endpoint that would have received the notification. device_id: type: string description: The target device identifier. data: type: string description: The JS-visible payload (post-transport-decryption).